Flutter Bloc/Cubit如何回退至前状态?登录加载状态未消失问题
问题描述
我有一个signIn函数,会从API获取数据:请求成功则跳转至其他页面,失败则显示AlertDialog。发起请求前,我会切换至WaitingSignInAuth状态,显示CircularProgressIndicator提示用户正在加载数据。但关闭错误提示的AlertDialog后,CircularProgressIndicator并未消失,我希望移除WaitingSignInAuth状态,回到带有输入框的Scaffold页面。此外,触发错误时我已emit ErrorSignInAuth状态,为何WaitingSignInAuth状态仍存在?ErrorSignInAuth为何未替换它,还是其工作逻辑本就如此?
相关代码:
@override Widget build(BuildContext context) { return BlocConsumer<AuthCubit, AuthState>( listener: (context, state) { if (state is WaitingSignInAuth) { showDialog( context: context, builder: (context) => Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, color: Colors.black.withOpacity(0.6), child: const Center( child: CircularProgressIndicator( strokeWidth: 1, color: Colors.black, backgroundColor: Colors.white, ), ), )); } if (state is ErrorSignInAuth) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text("Bad request"), content: SingleChildScrollView( child: ListBody( children: <Widget>[ Text("Error") ], ), ), actions: <Widget>[ TextButton( child: const Text("Close"), onPressed: () { Navigator.pop(context); // 点击关闭后AlertDialog消失,但加载圈还在 }, ) ], ); } ); } }, ); }
效果截图:
问题原因
- Bloc状态是替换式的:
ErrorSignInAuth已经完全替换了WaitingSignInAuth状态,Bloc内部不会同时存在多个状态。你看到加载圈还在,是因为加载弹窗是通过showDialog打开的独立弹窗,状态切换后没有主动关闭它。 - 弹窗入栈逻辑:
showDialog会将弹窗加入导航栈,WaitingSignInAuth触发的加载弹窗是第一个栈元素,ErrorSignInAuth触发的错误弹窗是第二个栈元素。关闭错误弹窗只是弹出了第二个栈元素,加载弹窗仍在栈中,所以不会消失。
解决方案
方案一:切换到错误状态时先关闭加载弹窗
在进入ErrorSignInAuth状态的分支里,先调用Navigator.pop(context)关闭之前的加载弹窗,再打开错误提示弹窗:
@override Widget build(BuildContext context) { return BlocConsumer<AuthCubit, AuthState>( listener: (context, state) { if (state is WaitingSignInAuth) { showDialog( context: context, barrierDismissible: false, // 禁止点击空白关闭加载弹窗 builder: (context) => Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, color: Colors.black.withOpacity(0.6), child: const Center( child: CircularProgressIndicator( strokeWidth: 1, color: Colors.black, backgroundColor: Colors.white, ), ), )); } if (state is ErrorSignInAuth) { // 先关闭加载弹窗 Navigator.pop(context); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text("Bad request"), content: const SingleChildScrollView( child: ListBody( children: <Widget>[ Text("Error") ], ), ), actions: <Widget>[ TextButton( child: const Text("Close"), onPressed: () { Navigator.pop(context); }, ) ], ); } ); } }, ); }
方案二:用BlocBuilder管理加载UI(更推荐)
避免在listener里处理弹窗,而是用BlocBuilder直接在页面中渲染加载遮罩,这样状态切换时UI会自动更新:
@override Widget build(BuildContext context) { return Stack( children: [ // 原有的登录页面Scaffold Scaffold( // 你的输入框、登录按钮等内容 ), // 根据状态渲染加载遮罩 BlocBuilder<AuthCubit, AuthState>( builder: (context, state) { if (state is WaitingSignInAuth) { return Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, color: Colors.black.withOpacity(0.6), child: const Center( child: CircularProgressIndicator( strokeWidth: 1, color: Colors.black, backgroundColor: Colors.white, ), ), ); } return const SizedBox.shrink(); }, ), // 错误弹窗的监听 BlocListener<AuthCubit, AuthState>( listener: (context, state) { if (state is ErrorSignInAuth) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text("Bad request"), content: const SingleChildScrollView( child: ListBody( children: <Widget>[ Text("Error") ], ), ), actions: <Widget>[ TextButton( child: const Text("Close"), onPressed: () { Navigator.pop(context); }, ) ], ); } ); } }, ), ], ); }
补充说明
- 方案二的优势是UI状态完全由Bloc状态驱动,不需要手动管理弹窗的打开和关闭,状态切换时加载遮罩会自动消失,更符合Bloc的设计思想。
- 如果使用
showDialog的方式,建议给加载弹窗设置barrierDismissible: false,避免用户误触关闭加载弹窗导致状态不一致。
内容的提问来源于stack exchange,提问作者newbiras
相关产品推荐
相关产品推荐

