Flutter:ChangeNotifier 侦听器有时会在获得新值之前用旧值刷新

问题描述

我正在构建一个 table_calendar 小部件,我想在该小部件上选择多天并将它们保存在 Firestore 中。 (所选日期为绿色,其他均为白色)

enter image description here

我通过流提供程序从 firestore 获取值,并使用 ChangeNotifier 在需要的地方读取这些值,然后使用 _selectedDays.addAll(来自 table_calendar)将所有值添加到日历中。我的问题是,有时,当我尝试取消选择某一天时,它会从 Firebase 中删除,但是 ChangeNotifier 侦听器在使用新值刷新之前会先使用旧值刷新,这意味着该日期仍然被选中,即使它应该不会。 当我停止测试侦听器时。

final events = watch(todoChangeNotifierProvider).todos;

我可以看到有 60-70% 的时间它会获得新值,但有时它会用旧值刷新,搞砸了 table_calendar 用户界面。

为什么会这样?

我在全球范围内声明了提供者

final todoChangeNotifierProvider = ChangeNotifierProvider.autodispose<TodosProvider>((ref) {
  return TodosProvider();
});

TodosProvider 看起来像这样

class TodosProvider extends ChangeNotifier {

  final FirebaseAuth auth = FirebaseAuth.instance;

  List<Todo> _todos = [];

  void setTodos(List<Todo> todos) {
    _todos = todos;
  }

我正在流中设置 Change Notifier 值,因此如果发生变化,它会更新。

编辑:更多代码 使用流值设置 ChangeNotifer

Widget build(BuildContext context,ScopedReader watch) {
    final todoProvider = watch(todoChangeNotifierProvider);
    //Todo? currentTodo = Todo();
    final firebaseAuth = context.read(firebaseAuthProvider);
    final todoStream = watch(todoStreamProvider(firebaseAuth.currentUser!.uid));
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,title: Text('DASHBOARD'),),body: todoStream.when(
          data: (data) {
            final currentTodo = data;
            todoProvider.setTodos(currentTodo);
            return SingleChildScrollView(
              padding: EdgeInsets.fromLTRB(10,10,55),child: Column(
                children: [
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.center,//Center Column contents horizontally,children: [
                      Row(
                        mainAxisAlignment: MainAxisAlignment.center,//Center Row contents horizontally,children: [Text('${currentUser!.loginStreak}',style: TextStyle(fontSize: 20)),Icon(Icons.local_fire_department,size: 40,color: Color(0xFFFD8787),Text('DAILY ACTIVITY STREAK',style: TextStyle(fontSize: 20),)],],Column(
                   children: [
                     TodoListWidget(editAllowed: true,currentUser: currentUser,)
                   ],)

读取提供者

class TodoListWidget extends ConsumerWidget {
  final UserData? currentUser;
  final editAllowed;
  TodoListWidget({this.editAllowed,this.currentUser});

  @override
  Widget build(BuildContext context,ScopedReader watch) {
        final todoProvider = watch(todoChangeNotifierProvider);
        final todos = todoProvider.todos;
        return todos.isEmpty
...

选择日期、标记并更新 Firebase

void _onDaySelected(DateTime selectedDay,DateTime focusedDay) {
setState(() {
if (_selectedDays.contains(selectedDay)) {
        docRef.update({
          'calendarEvents': FieldValue.arrayRemove([selectedDay])
        });
        userPointsRef.update({'points': FieldValue.increment(-1)});
        habitPointsRef
            .update({'daysTrackedThisWeek': FieldValue.increment(-1)});
        _selectedDays.remove(selectedDay);
      } else {
        //INCREMENTS POITNS AND DAYS TRACKED ON SELECTED DAY

        docRef.update({
          'calendarEvents': FieldValue.arrayUnion([selectedDay])
        });
        userPointsRef.update({'points': FieldValue.increment(1)});
        habitPointsRef.update({'daysTrackedThisWeek': FieldValue.increment(1)});
        _selectedDays.add(selectedDay);
      }
    });
enter code here

尝试画出更好的画面。 我通过流从 firebase 获取 table_calendar 值。 我将值设置为 ChangeNotifier 我阅读了 ChangeNotifier(但似乎有 40% 的时间,当新值出现时,它首先用旧值刷新,然后再获得新值(这会弄乱用户界面)

这就是我为 table_calendar 选定天数设置值的方式 在 TodoWidget 中

 Widget build(BuildContext context) {
    _selectedDays.addAll(widget.todo!.calendarEvents);

值来自 TodoListWidget

解决方法

必须notifyListenersChangeNotifier 一起使用。当您查看提供程序时,它只知道在调用 notifyListeners 时获取新值。

执行此操作时出现运行时错误的原因是您在初始构建期间触发了更新。

在您的 ChangeNotifier 中,添加对 notifyListeners 的调用:

void setTodos(List<Todo> todos) {
  _todos = todos;
  notifyListeners();
}

在您的小部件代码中:

Widget build(BuildContext context,ScopedReader watch) {
  final firebaseAuth = watch(firebaseAuthProvider);
  final todoStream = watch(todoStreamProvider(firebaseAuth.currentUser!.uid));
  
  return Scaffold(
    appBar: AppBar(
      centerTitle: true,title: Text('DASHBOARD'),),body: todoStream.when(
      data: (currentTodo) {
        WidgetsBinding.instance?.addPostFrameCallback((_) {
          context.read(todoChangeNotifierProvider).setTodos(currentTodo);
        });
        return SingleChildScrollView(...);
}

PostFrameCallback 完全符合它的要求 - 将代码从运行延迟到下一帧,这将消除您面临的运行时错误。