Flutter Riverpod结合HookWidget如何用ProviderListener显示SnackBar
解决方法
核心问题说明
你当前报错的核心原因有两个:
useProvider返回状态的when方法是专门用于构建Widget的,所有回调分支必须返回Widget类型,你在回调中执行弹出SnackBar的侧效逻辑不符合方法设计要求- 直接在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
相关产品推荐
相关产品推荐

