Flutter:ValueListenableNotifier值变更时build期调用showDialog报错解决
解决
setState() or markNeedsBuild() called during build错误并正确显示对话框 问题背景
通过AppValueNotifier创建了showStreamDialogNotifier变量,其值随某个流变化,希望值变为true时在Login表单上显示对话框,但调用showDialog时触发了setState() or markNeedsBuild() called during build错误。
错误原因
- 构建流程中触发UI更新:
ValueListenableBuilder的builder方法属于Widget构建流程的一部分,直接在其中调用showDialog会触发新的UI状态更新,和当前正在执行的build流程冲突,导致报错。 AppValueNotifier实例重复创建:在Login的build方法内实例化AppValueNotifier,每次组件build都会生成新实例,不仅丢失之前的状态,还会导致监听失效。- 错误更新
ValueNotifier:updateShowStreamDialogNotifier方法直接重建了ValueNotifier实例,而非修改现有实例的value属性,导致之前的监听者无法收到状态更新通知。
解决方案
1. 修复AppValueNotifier的状态更新逻辑
修改updateShowStreamDialogNotifier方法,直接修改ValueNotifier的value,同时把对话框标题和消息整合到类中统一管理:
class AppValueNotifier { final ValueNotifier<bool> showStreamDialogNotifier = ValueNotifier<bool>(false); String streamDialogTitle = ''; String streamDialogMsg = ''; void updateShowStreamDialogNotifier({ required bool? newShowStreamDialog, required String? newStreamDialogTitle, required String? newStreamDialogMsg, }) { if (newShowStreamDialog != null) { // 直接修改value,而非重建ValueNotifier showStreamDialogNotifier.value = newShowStreamDialog; } if (newStreamDialogTitle != null) { streamDialogTitle = newStreamDialogTitle; } if (newStreamDialogMsg != null) { streamDialogMsg = newStreamDialogMsg; } } }
2. 修复Login组件的监听与对话框触发逻辑
- 将
AppValueNotifier实例提升为State类的成员变量,避免每次build重建 - 使用
WidgetsBinding.instance.addPostFrameCallback延迟执行对话框,确保当前build流程完成后再触发 - 调整对话框关闭时的状态更新逻辑,确保和
AppValueNotifier同步
修改后的Login组件代码:
class Login extends StatefulWidget { const Login({super.key}); @override State<Login> createState() => _LoginState(); } class _LoginState extends State<Login> { // 作为State成员,确保组件生命周期内只有一个实例 final AppValueNotifier appValueNotifier = AppValueNotifier(); Future<void> showStreamDialog({ required BuildContext context, required String title, required String msg, }) { return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text(title), content: SingleChildScrollView( child: ListBody( children: <Widget>[Text(msg)], ), ), actions: <Widget>[ TextButton( child: const Text('OK'), onPressed: () { // 关闭对话框后重置状态 appValueNotifier.updateShowStreamDialogNotifier( newShowStreamDialog: false, newStreamDialogTitle: '', newStreamDialogMsg: '', ); Navigator.pop(context); }, ), ], ); }, ); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( children: [ Expanded( child: ValueListenableBuilder<bool>( valueListenable: appValueNotifier.showStreamDialogNotifier, builder: (context, value, child) { if (value == true) { // 在当前build完成后执行对话框,避免冲突 WidgetsBinding.instance.addPostFrameCallback((_) { showStreamDialog( context: context, title: appValueNotifier.streamDialogTitle, msg: appValueNotifier.streamDialogMsg, ); }); } return const Text(''); }, ), ), ], ), ), ); } }
补充说明
如果原本使用GlobalsProvider管理全局状态,需要确保AppValueNotifier和GlobalsProvider的状态同步,或者直接将对话框相关状态整合到GlobalsProvider中,避免状态分散。
内容的提问来源于stack exchange,提问作者TheMortiestMorty
相关产品推荐
相关产品推荐

