Flutter使用ValueNotifier在页面build阶段更新文本抛出异常如何解决
问题解决方法
错误根因
你在_buildRow方法中直接修改_myPercentage.value,而_buildRow是ListView.builder构建列表项的回调方法,属于当前页面的build流程内。此时修改ValueNotifier会触发监听它的ValueListenableBuilder调用setState重绘,Flutter禁止在build过程中触发其他组件重绘,因此抛出异常。
解决方案
保留ValueNotifier的前提下,调整计算和更新逻辑即可:
- 把逐行扣减占比的逻辑,改成在FutureBuilder拿到完整用户数据后批量计算总占比,再一次性更新剩余百分比
- 把首次更新ValueNotifier的操作推到当前帧绘制完成后执行,避开build流程
- 移除
_buildRow中修改_myPercentage.value的代码,避免build过程中触发状态变更
修改后的核心代码
1. 调整_buildUsersFromDB方法
Widget _buildUsersFromDB() { return FutureBuilder<List<User>>( future: _userDAO.readData(), builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) { if (snapshot.hasData) { // 批量计算所有用户的总占比 final totalUserPercent = snapshot.data!.fold<double>(0, (prev, user) => prev + user.percentage); final remainingPercent = 100 - totalUserPercent; // 推到当前帧绘制完成后更新,避免build过程中触发重绘 WidgetsBinding.instance.addPostFrameCallback((_) { _myPercentage.value = remainingPercent; }); return ListView.builder( itemCount: snapshot.data!.length, shrinkWrap: true, itemBuilder: (BuildContext context, int i) { return Card( elevation: 2, margin: const EdgeInsets.all(5), child: _buildRow(snapshot.data![i]), ); }, ); } return const Center(child: CircularProgressIndicator()); }, ); }
2. 清理_buildRow方法
移除里面修改_myPercentage.value的代码即可,其他逻辑保持不变。
3. 后续用户修改占比的逻辑适配
用户点击列表项修改占比时,属于用户交互回调,不在build流程内,可以直接更新ValueNotifier,示例:
onTap: () => _showDoubleDialog(user).then((newPercent) { if (newPercent != null && newPercent != user.percentage) { // 计算占比差值 final diff = newPercent - user.percentage; // 直接更新剩余占比,会自动触发环形进度条文本刷新 _myPercentage.value -= diff; // 这里补充更新数据库user.percentage的逻辑即可 } }),
build阶段更新状态的最佳实践
- build相关的回调方法(包括build函数、各类Builder的构建回调)应该是纯函数,仅根据当前状态返回Widget,不要在内部执行修改状态、触发重绘的副作用操作
- 如果需要在页面渲染完成后更新状态,使用
WidgetsBinding.instance.addPostFrameCallback包裹更新逻辑,该回调会在当前帧所有绘制任务结束后执行,不会和build流程冲突 - 尽量批量计算状态后再一次性更新,不要频繁修改监听类状态,减少不必要的重绘
内容的提问来源于stack exchange,提问作者lele
相关产品推荐
相关产品推荐

