Flutter报错:setState()/markNeedsBuild()在build期间调用的解决求助
问题描述
我在实现ToggleButton组件时遇到了报错:'setState() or markNeedsBuild() called during build',需求是将激活的ToggleButton索引存储到列表中,以便在其他屏幕展示相同的选中状态。相关代码如下:
class ToggleButtonUsers extends StatefulWidget { @override _ToggleButtonUsersState createState() => _ToggleButtonUsersState(); } class _ToggleButtonUsersState extends State<ToggleButtonUsers> { bool vertical = false; List<bool> selectedUser = []; List<Widget> selectedChildren = []; @override void initState() { final userModelData = Provider.of<UserModel>(context, listen: false); final userModel = userModelData.items; final toggleModelData = Provider.of<ToggleProvider>(context, listen: false); final toggleModel = toggleModelData.items; final indexUserData = Provider.of<ToggleProvider>(context, listen: false); final indexUser = indexUserData.indexUser; for (var index = 0; index < toggleModel.length; index += 1) { debugPrint('aktueller Index: ' + index.toString()); selectedUser = List.generate( toggleModel.length, (index) => index == 0 ? true : false, ); toggleModelData.addIndex(0); selectedChildren = List.generate( toggleModel.length, (index) => UserItem( userModel[index].id, userModel[index].name, userModel[index].image, userModel[index].color, ), ); //indexUserData.addIndex(0); } super.initState(); } @override Widget build(BuildContext context) { final indexUserData = Provider.of<ToggleProvider>(context); final indexUser = indexUserData.indexUser; debugPrint('Inhalt UserIndex ist: ' + indexUser.toString()); return Padding( padding: const EdgeInsets.all(8.0), child: ToggleButtons( constraints: const BoxConstraints( minHeight: 40.0, maxHeight: 45, minWidth: 80.0, maxWidth: 85, ), borderRadius: BorderRadius.circular(30), fillColor: Colors.green, //disabledBorderColor: Colors.red, direction: vertical ? Axis.vertical : Axis.horizontal, onPressed: (int index) { if (mounted) { setState(() { selectedUser[index] = !selectedUser[index]; }); } selectedUser[index] == true ? indexUserData.addIndex(index) : indexUserData.removeIndex(10); debugPrint('selected User ist nun: ' + selectedUser.toString()); debugPrint('der gecklickte Index ist: ' + index.toString()); }, children: selectedChildren, isSelected: selectedUser, ), ); } }
错误原因分析
- initState循环逻辑冗余:循环中每次都重新生成
selectedUser、selectedChildren,并多次调用toggleModelData.addIndex(0),导致Provider状态被频繁更新,而initState阶段Widget还在构建流程中,可能触发markNeedsBuild()调用时机错误。 - onPressed中Provider操作逻辑错误:移除索引时硬编码了
10,而非当前操作的index,且未判断索引是否存在于列表中;同时selectedUser的状态更新与Provider操作的顺序可能导致状态不同步。 - 初始化逻辑重复:循环次数等于
toggleModel.length,但selectedUser和selectedChildren只需要初始化一次即可,重复生成完全没必要。
修正方案及代码
修正步骤
- 移除initState中的循环,只初始化一次
selectedUser和selectedChildren; - 初始化Provider的indexUser列表时,只添加一次初始选中的索引(0);
- 修正onPressed中的移除逻辑,使用当前操作的index,并先判断索引是否存在再移除;
- 确保状态更新的顺序合理,先更新本地
selectedUser,再同步到Provider。
修正后的完整代码
class ToggleButtonUsers extends StatefulWidget { @override _ToggleButtonUsersState createState() => _ToggleButtonUsersState(); } class _ToggleButtonUsersState extends State<ToggleButtonUsers> { bool vertical = false; List<bool> selectedUser = []; List<Widget> selectedChildren = []; @override void initState() { super.initState(); // 先调用super.initState(),符合Flutter生命周期规范 final userModelData = Provider.of<UserModel>(context, listen: false); final userModel = userModelData.items; final toggleModelData = Provider.of<ToggleProvider>(context, listen: false); final toggleModel = toggleModelData.items; final indexUserData = Provider.of<ToggleProvider>(context, listen: false); // 只初始化一次selectedUser,默认第一个按钮选中 selectedUser = List.generate( toggleModel.length, (index) => index == 0, ); // 初始化选中的子组件列表 selectedChildren = List.generate( toggleModel.length, (index) => UserItem( userModel[index].id, userModel[index].name, userModel[index].image, userModel[index].color, ), ); // 清空原有索引列表,添加初始选中的索引0(避免重复添加) indexUserData.indexUser.clear(); indexUserData.addIndex(0); debugPrint('初始选中索引: 0'); } @override Widget build(BuildContext context) { final indexUserData = Provider.of<ToggleProvider>(context); final indexUser = indexUserData.indexUser; debugPrint('当前存储的选中索引: ' + indexUser.toString()); return Padding( padding: const EdgeInsets.all(8.0), child: ToggleButtons( constraints: const BoxConstraints( minHeight: 40.0, maxHeight: 45, minWidth: 80.0, maxWidth: 85, ), borderRadius: BorderRadius.circular(30), fillColor: Colors.green, direction: vertical ? Axis.vertical : Axis.horizontal, onPressed: (int index) { setState(() { selectedUser[index] = !selectedUser[index]; }); // 根据新的选中状态更新Provider中的索引列表 if (selectedUser[index]) { if (!indexUser.contains(index)) { // 避免重复添加 indexUserData.addIndex(index); } } else { if (indexUser.contains(index)) { // 存在才移除 indexUserData.removeIndex(index); } } debugPrint('当前选中状态: ' + selectedUser.toString()); debugPrint('点击的索引: ' + index.toString()); }, children: selectedChildren, isSelected: selectedUser, ), ); } }
额外注意事项
- 确保你的
ToggleProvider中的addIndex和removeIndex方法正确实现,并且在修改列表后调用notifyListeners()来通知UI更新; - 如果
ToggleProvider的removeIndex方法是根据值移除而非索引,需要确认方法逻辑(比如实现为void removeIndex(int value) { items.remove(value); notifyListeners(); }); - 生产环境中建议移除不必要的
debugPrint语句,避免性能开销。
内容的提问来源于stack exchange,提问作者Pauer Auer
相关产品推荐
相关产品推荐

