Flutter pop关闭圆形进度指示器误关ListView的解决方案
问题成因
- 核心问题是操作的Navigator实例和弹窗所属的Navigator实例不匹配。Flutter中
Navigator.of(context)会沿着组件树向上查找离传入context最近的Navigator实例作为操作对象:你调用showDialog弹出加载框时,该方法默认useRootNavigator: true,会把加载弹窗压入应用根Navigator(MaterialApp提供的顶层导航)的栈顶;但你调用pop关闭弹窗时传入的context来自ListView内部的表单项,如果你的自定义表单项FieldBuilderForm或页面内其他组件嵌套了独立的子Navigator,此时Navigator.of(context)拿到的是这个子Navigator的实例,调用pop只会操作子Navigator的路由栈,根本碰不到根Navigator上的加载弹窗,反而会把子Navigator栈顶的表单路由弹出,表现出来就是加载框没关,ListView组件被异常移除。 - 存在时序隐患:你调用
buildShowDialog时没有加await等待弹窗完成入栈,如果网络请求响应速度极快,会出现弹窗还没被压入导航栈就执行pop,误关闭栈内其他正常页面的问题。 - 代码写法不严谨:弹出弹窗时没有显式声明导航栈归属,关闭时也没有指定要操作的Navigator实例,完全依赖context自动查找,在组件嵌套较深的页面很容易出现上下文匹配错误。
修复方案
按以下步骤修改即可解决问题:
- 改造加载弹窗方法,显式指定弹窗压入根导航栈,避免弹窗归属错误
- 调用弹窗方法时加await等待弹窗入栈完成,再发起网络请求,避免时序问题
- 关闭加载弹窗时显式指定操作根Navigator,确保找到的是加载框所属的导航实例
- 后续所有弹窗(包括错误提示弹窗)都显式配置
useRootNavigator: true,统一弹窗的导航栈归属
修改后的核心代码如下:
// 改造后的加载弹窗方法 Future buildShowDialog(BuildContext context) { return showDialog( context: context, barrierDismissible: false, // 显式指定弹窗压入根导航栈 useRootNavigator: true, builder: (BuildContext dialogContext) { return Center( child: LoadingAnimationWidget.threeArchedCircle( color: Colors.white, size: 50), ); }, ); } loginFunction(http.Client client, jsonBody) async { // 等待加载弹窗完成入栈再执行后续逻辑 await buildShowDialog(context); var url = Uri.parse("https://my-office-timesheet.herokuapp.com/api/signin"); var respsonse = await client.post(url, body: jsonBody); if (respsonse.statusCode == 200) { // 显式指定操作根导航栈关闭加载框,避免找错子Navigator Navigator.of(context, rootNavigator: true).pop(); if (this.mounted) { setState(() { jsonResponse = Post.fromJson(json.decode(respsonse.body)); }); } if (jsonResponse!.success == true) { if (this.mounted) { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (BuildContext context) => Dashboard()), (Route<dynamic> route) => false); } } else { showDialog( context: context, // 错误提示弹窗也指定压入根导航栈 useRootNavigator: true, builder: (ctx) => AlertDialog( title: Text("Message:"), content: Text("${jsonResponse?.message}"), actions: [ ElevatedButton( onPressed: () { // 这里用弹窗自身的ctx关闭,不会出现匹配错误 Navigator.of(ctx).pop(); if (this.mounted) { setState(() { jsonResponse = null; }); } }, child: const Text("Ok"), ), ], ), ); } } return respsonse; }
可选优化
如果需要更稳妥的弹窗控制,可以在弹出加载框时缓存弹窗自身的context,关闭时直接用缓存的context操作,完全避免外部context带来的匹配问题:
// 新增页面级变量缓存加载框上下文 BuildContext? _loadingContext; Future buildShowDialog(BuildContext context) { return showDialog( context: context, barrierDismissible: false, useRootNavigator: true, builder: (BuildContext dialogContext) { _loadingContext = dialogContext; return Center( child: LoadingAnimationWidget.threeArchedCircle( color: Colors.white, size: 50), ); }, ); } // 关闭加载框时使用缓存的context if (_loadingContext != null && _loadingContext!.mounted) { Navigator.of(_loadingContext!).pop(); _loadingContext = null; }
内容的提问来源于stack exchange,提问作者Jeryn Immanuel
相关产品推荐
相关产品推荐

