Flutter Provider ChangeNotifier构建期setState报错解决
setState() or markNeedsBuild() called during build 问题排查与修复 问题1:notifyListeners的更新触发逻辑
ChangeNotifier本身仅维护监听者列表、在收到notifyListeners调用时逐个回调监听方法,自身不会主动触发setState或markNeedsBuild。
触发组件重建的逻辑在Provider侧:当使用Provider.of<T>(context, listen: true)(listen参数默认值为true)获取状态时,Provider会将当前context注册为对应ChangeNotifier的监听者,后续收到notifyListeners的信号时,Provider会对所有注册的context调用markNeedsBuild,标记组件需要在下一帧重建。
你遇到的"called during build"报错,本质是当前组件还处于构建执行阶段,就触发了notifyListeners,Provider尝试标记组件重建,但框架不允许构建过程中再次标记需要重建,直接抛出异常。
问题2:报错根因与修复方案
核心根因
你的报错和UI层写的Provider.of(..., listen: false)没有关系,问题出在unSeen getter的错误实现:你在遍历计算未读数量时,每匹配到一条未读通知就调用一次notifyListeners()。getter是同步读操作,只要组件构建时读取unSeen的值,就会在build流程执行中途触发更新通知,直接触发框架的构建时状态更新校验报错。
修复步骤
- 移除getter中的notifyListeners调用,保证getter是无副作用的纯计算逻辑
notifyListeners仅能在通知列表数据发生真实变更的时机调用(比如拉取到新通知、标记通知已读/未读、删除通知等操作完成后),绝对不能在属性读取、build执行过程中调用。
修正后的unSeen getter代码:
正确的notifyListeners调用示例(对应数据修改方法):int get unSeen { int notSeen = 0; for(Notification notification in _notifications) { if(!notification.isSeen) { notSeen++; } } return notSeen; }// 新增通知时 void addNotification(Notification notification) { _notifications.add(notification); notifyListeners(); // 数据修改完成后再通知更新 } // 标记全部通知已读时 void markAllSeen() { for(var notification in _notifications) { notification.isSeen = true; } notifyListeners(); // 批量修改完成后统一调用一次即可 } - 优化UI层状态获取逻辑,避免不必要的rebuild
你当前写的listen: false虽然不会触发监听注册,但会导致未读数变化时Badge无法自动更新,推荐用Selector做局部精准监听,只在unSeen值变化时刷新Badge组件,性能更好:BottomNavigationBarItem( icon: Selector<NotificationsModel, int>( selector: (context, notiModel) => notiModel.unSeen, builder: (context, unSeenCount, iconChild) { return Badge( showBadge: unSeenCount > 0, badgeContent: Text( unSeenCount.toString(), style: TextStyle( color: Colors.white, fontFamily: kFontFamily, fontWeight: FontWeight.w600, fontSize: Dimensions.font7, ), ), child: iconChild, ); }, child: Icon(dModel.index == 2 ? Icons.notifications : Icons.notifications_paused_outlined), ), label: 'Notifications', ),
补充避坑:如果需要在组件初始化阶段触发数据拉取等会调用notifyListeners的逻辑,不要直接写在build方法里,可以放到
WidgetsBinding.instance.addPostFrameCallback中执行,等当前帧构建完成后再触发更新,也能避免同类报错。
内容的提问来源于stack exchange,提问作者public static void Main

