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

Flutter Riverpod结合HookWidget如何用ProviderListener显示SnackBar

解决方法

核心问题说明

你当前报错的核心原因有两个:

  1. useProvider返回状态的when方法是专门用于构建Widget的,所有回调分支必须返回Widget类型,你在回调中执行弹出SnackBar的侧效逻辑不符合方法设计要求
  2. 直接在build同步执行逻辑中调用showSnackBar属于build过程中触发UI交互侧效,会触发Flutter框架异常

方案1:使用ConsumerListener(原ProviderListener)实现

这是Riverpod官方推荐的监听状态变化执行侧效的方案,Riverpod 1.0+版本更名ConsumerListener,0.14及更早版本使用ProviderListener即可,API完全一致。

修改后的页面代码如下:

class Profile extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final nameFamily = useTextEditingController();
    // 仅用于构建UI的状态读取,不需要监听变化执行侧效
    final profileState = useProvider(profileProvider);

    return Container(
      color: DefaultColors.$darkBlue,
      child: SafeArea(
        child: Directionality(
          textDirection: TextDirection.rtl,
          // 新增ConsumerListener监听状态变化
          child: ConsumerListener(
            provider: profileProvider,
            // listener回调会在帧构建完成后执行,不会触发build异常
            listener: (context, state) {
              state.whenOrNull(
                success: (value) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('资料更新成功'))
                  );
                },
                error: (error, stackTrace) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('更新失败:$error'))
                  );
                },
              );
            },
            child: Scaffold(
              body: ListView(
                children: [
                  Form(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.start,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        profileState.when(
                          idle: () => const SizedBox.shrink(),
                          // 可根据需求调整loading UI
                          loading: () => const Center(child: CircularProgressIndicator()),
                          success: (value) => const SizedBox.shrink(),
                          error: (error, stackTrace) => const SizedBox.shrink(),
                        ),
                        // 你的其他表单组件
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

方案2:结合useEffect实现(更适配HookWidget)

如果你更习惯使用Hook的API,可以用useEffect监听状态变化,配合帧回调实现侧效:

class Profile extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final nameFamily = useTextEditingController();
    final profileState = useProvider(profileProvider);

    // 新增useEffect监听状态变化
    useEffect(() {
      // 确保在当前帧构建完成后执行SnackBar弹出逻辑
      SchedulerBinding.instance.addPostFrameCallback((_) {
        profileState.whenOrNull(
          success: (value) {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('资料更新成功'))
            );
          },
          error: (error, stackTrace) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('更新失败:$error'))
            );
          },
        );
      });
      return null;
    }, [profileState]); // 状态变化时触发effect

    return Container(
      color: DefaultColors.$darkBlue,
      child: SafeArea(
        child: Directionality(
          textDirection: TextDirection.rtl,
          child: Scaffold(
            body: ListView(
              children: [
                Form(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.start,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      profileState.when(
                        idle: () => const SizedBox.shrink(),
                        loading: () => const Center(child: CircularProgressIndicator()),
                        success: (value) => const SizedBox.shrink(),
                        error: (error, stackTrace) => const SizedBox.shrink(),
                      ),
                      // 你的其他表单组件
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

注意事项

  • when方法的所有分支必须返回Widget,无对应UI需要渲染时返回SizedBox.shrink()即可
  • 所有弹出SnackBar、路由跳转这类侧效逻辑,禁止直接在build同步流程中执行,必须放在帧回调或专门的侧效监听回调中
  • 如果使用的是Riverpod 0.14.x及更早版本,将方案1中的ConsumerListener替换为ProviderListener即可正常使用

内容的提问来源于stack exchange,提问作者DolDurma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 07:42:03