Flutter+Riverpod:如何根据Provider状态显示加载弹窗?
问题描述
我基于Flutter和Riverpod开发应用,创建了如下的AuthController的StateNotifierProvider:
final authControllerProvider = StateNotifierProvider<AuthController, bool>((ref) { final authApi = ref.watch(authApiProvider); final userApi = ref.watch(userApiProvider); final localStorage = ref.watch(localStorageProvider); return AuthController(authAPI: authApi, userAPI: userApi, localStorage: localStorage); });
这个StateNotifierProvider的状态是布尔值,用来标记是否处于加载状态,在AuthController内部通过state = false或state = true切换状态:
class AuthController extends StateNotifier<bool> implements IAuthController { // .... void signupWithGoogle(....) { state = true; // 执行所有注册操作 state = false; } }
在视图层可以通过ref.watch(authControllerProvider)读取状态:
bool isLoading = ref.watch(authControllerProvider);
我遇到的问题是:如何根据这个状态值显示/关闭加载弹窗?
最后尝试的代码导致屏幕全黑且无报错:
@override Widget build(BuildContext context) { final isLoading = ref.watch(authControllerProvider); if (isLoading) { WidgetsBinding.instance.addPostFrameCallback((_) { showLoadingAnimation(context); }); } else { WidgetsBinding.instance.addPostFrameCallback((_) { closeLoadingAnimation(context); }); } ....
解决方案
你的问题核心是在build方法里直接调用弹窗方法会触发重复调用,且上下文处理不当导致黑屏。推荐两种可靠的实现方式:
方式一:使用Overlay加载组件(推荐)
不要用弹窗的方式,而是在页面的Stack里根据isLoading状态渲染加载遮罩,完全避免弹窗的上下文问题:
@override Widget build(BuildContext context) { final isLoading = ref.watch(authControllerProvider); return Scaffold( body: Stack( children: [ // 你的页面主体内容 YourMainContent(), // 加载遮罩 if (isLoading) Container( color: Colors.black54, child: const Center( child: CircularProgressIndicator(), ), ), ], ), ); }
这种方式不需要额外管理弹窗的显示/关闭,完全由Riverpod状态驱动,不会出现重复调用或上下文错误。
方式二:使用ref.listen监听状态变化
如果一定要用弹窗,不要在build方法里处理,而是用ref.listen监听状态变化,只在状态切换时执行一次弹窗操作:
@override void initState() { super.initState(); // 监听authController的状态变化 ref.listen<bool>(authControllerProvider, (previous, next) { if (next) { // 显示加载弹窗,注意用Navigator的方式管理,避免重复弹出 showDialog( context: context, barrierDismissible: false, builder: (_) => const Center(child: CircularProgressIndicator()), ); } else { // 关闭弹窗,确保当前弹窗存在时才关闭 if (Navigator.canPop(context)) { Navigator.pop(context); } } }); } @override Widget build(BuildContext context) { // 这里只渲染页面内容,不需要处理弹窗逻辑 return Scaffold( body: YourMainContent(), ); }
注意:
- 用
ref.listen而非在build里处理,避免每次build都触发弹窗操作 - 关闭弹窗前判断
Navigator.canPop,防止重复关闭导致错误 - 弹窗的
barrierDismissible设为false,避免用户误触关闭
为什么你的代码会黑屏?
你在build方法里每次状态变化都会调用addPostFrameCallback,导致多次调用showLoadingAnimation和closeLoadingAnimation,可能重复弹出弹窗或者上下文混乱,最终导致UI渲染异常。另外,如果你自己实现的showLoadingAnimation没有正确管理弹窗栈,也会导致黑屏。
内容的提问来源于stack exchange,提问作者Gustavo Bordin
相关产品推荐
相关产品推荐

