You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 01:23:29