Flutter Riverpod疑问:为何需热重载才更新UI?我犯了什么错误?
问题:Flutter Riverpod状态更新后UI不刷新,仅热重载生效
我使用Flutter Riverpod进行状态管理,点击时通过ref.read(titleBlurProvider.notifier).lightUpPlayTitle()执行函数,控制台能看到打印输出,但UI仅在热重载后才更新。请问为何必须热重载才能更新UI?我哪里出错了?
完整代码:
class StartPage extends StatelessWidget { const StartPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ Image.asset('assets/matches_background.jpeg'), const TitlesWidget() ], ), ); } } class TitlesWidget extends StatelessWidget { const TitlesWidget({super.key}); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TitleWidget( text: 'Play', index: 0, callOnTap: () { Navigator.pushReplacement( context, MaterialPageRoute( builder: ((context) => const PositionedPage()))); }, ), TitleWidget( text: 'Leads', index: 1, callOnTap: () {}, ) ], ), ); } } class TitleWidget extends ConsumerWidget { TitleWidget( {super.key, required this.text, required this.callOnTap, required this.index}); String text; Function callOnTap; int index = 0; @override Widget build(BuildContext context, WidgetRef ref) { final double opacity = ref.watch(titleBlurProvider)[index]; return Consumer( builder: (context, ref, child) => Stack( children: [ Opacity( opacity: opacity, child: ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), child: Text( text, style: const TextStyle( fontSize: 80, fontWeight: FontWeight.bold, color: Colors.white), textAlign: TextAlign.center, ), ), ), GestureDetector( onLongPress: () => debugPrint(opacity.toString()), onTap: () => ref.read(titleBlurProvider.notifier).lightUpPlayTitle(), child: Opacity( opacity: 0.8, child: Text( text, style: const TextStyle( fontSize: 80, fontWeight: FontWeight.bold, color: Colors.white), textAlign: TextAlign.center, ), ), ), ], ), ); } } final titleBlurProvider = StateNotifierProvider<TitleBlur, List<double>>( (ref) => TitleBlur(), ); class TitleBlur extends StateNotifier<List<double>> { TitleBlur() : super([0, 0]); startRegularBlur() { Timer.periodic(const Duration(), (timer) {}); } lightUpPlayTitle() { debugPrint('light up'); while (state[0] < 1) { Future.delayed(const Duration(milliseconds: 500)); state[0] = state[0] + 0.1; if (state[0] > 1) state[0] = 1; debugPrint(state[0].toString()); } } }
我尝试将Consumer移到组件树顶部,但没有效果。
问题原因与修复方案
核心错误点
直接修改StateNotifier的state列表元素,未触发状态更新
Riverpod的StateNotifier依赖state引用的变化来通知监听者刷新UI。你直接修改state[0] = state[0] + 0.1时,列表的引用并未改变,所以UI无法感知到状态变化,只有热重载时重新读取状态才会更新。同步while循环+无await的Future.delayed阻塞主线程
Future.delayed是异步操作,但你在同步while循环中调用它却没有用await,导致循环瞬间执行完毕,直接将state[0]加到1,同时阻塞主线程,UI完全没有机会刷新中间状态。
修复步骤
1. 正确更新StateNotifier的状态
修改lightUpPlayTitle方法,每次更新时创建新的列表(改变引用),并使用异步循环避免阻塞:
Future<void> lightUpPlayTitle() async { debugPrint('light up'); // 用异步循环替代同步while,配合await等待延迟 while (state[0] < 1) { await Future.delayed(const Duration(milliseconds: 500)); // 创建新列表,替换旧state,触发状态更新 state = [ state[0] + 0.1 > 1 ? 1.0 : state[0] + 0.1, state[1], ]; debugPrint(state[0].toString()); } }
2. 简化Widget代码(可选)
TitleWidget已经是ConsumerWidget,内部的Consumer是多余的,可以移除,同时优化类型定义:
class TitleWidget extends ConsumerWidget { TitleWidget({ super.key, required this.text, required this.callOnTap, required this.index, }); final String text; final VoidCallback callOnTap; // 用类型更安全的VoidCallback替代Function final int index; @override Widget build(BuildContext context, WidgetRef ref) { final double opacity = ref.watch(titleBlurProvider)[index]; return Stack( children: [ Opacity( opacity: opacity, child: ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), child: Text( text, style: const TextStyle( fontSize: 80, fontWeight: FontWeight.bold, color: Colors.white, ), textAlign: TextAlign.center, ), ), ), GestureDetector( onLongPress: () => debugPrint(opacity.toString()), onTap: () => ref.read(titleBlurProvider.notifier).lightUpPlayTitle(), child: Opacity( opacity: 0.8, child: Text( text, style: const TextStyle( fontSize: 80, fontWeight: FontWeight.bold, color: Colors.white, ), textAlign: TextAlign.center, ), ), ), ], ); } }
关键注意事项
- StateNotifier的
state必须是不可变对象,或者每次更新都创建新的引用(比如列表、自定义对象要重新实例化),才能触发UI刷新。 - 异步操作必须配合
await使用,否则会导致逻辑异常或主线程阻塞。 - 优先使用类型明确的回调(如
VoidCallback)替代泛型Function,提升代码安全性。
内容的提问来源于stack exchange,提问作者im-not-a-robot
相关产品推荐
相关产品推荐

