使用StateNotifier与StateNotifierProvider无法显示子任务加载状态
问题:Flutter Riverpod中无法显示子任务生成的加载状态
我在Flutter里用Riverpod实现子任务生成和展示,已经写了继承StateNotifier的SubtaskNotifier,状态类型为AsyncValue<List<String>>。点击按钮能成功生成子任务,但加载状态始终无法显示。需求如下:
- 显示按钮,点击触发子任务生成
- 加载过程中展示加载状态
- 有数据后隐藏按钮,显示子任务列表
现有代码
FutureProvider定义
final generateGoalTaskSubtasksControllerProvider = FutureProvider.family<List<String>, GoalTask>((ref, task) async { final chatAPIController = ref.read(chatAPIControllerProvider.notifier); final subtasks = await chatAPIController.generateGoalTaskSubtasksController(task); ref.read(goalTaskSubtasksListProvider.notifier).updateTasks(subtasks); return subtasks; });
SubtaskNotifier实现
class SubtaskNotifier extends StateNotifier<AsyncValue<List<String>>> { final GoalTask goalTask; final ProviderContainer container; SubtaskNotifier(this.container, this.goalTask) : super(AsyncValue.data([])); void generateSubtasks() { state = container.read(generateGoalTaskSubtasksControllerProvider(goalTask)); } }
StateNotifierProvider定义
final subtaskNotifierProvider = StateNotifierProvider.family<SubtaskNotifier, AsyncValue<List<String>>, GoalTask>((ref, task) { return SubtaskNotifier(ref.container, task); });
SubtaskGenerator Widget
class SubtaskGenerator extends ConsumerWidget { final GoalTask goalTask; const SubtaskGenerator({Key? key, required this.goalTask}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final theme = ref.read(themeProvider); final subtaskNotifier = ref.watch(subtaskNotifierProvider(goalTask).notifier); return subtaskNotifier.state.when( data: (subtasks) { return Column( children: [ if (subtasks.isEmpty) Center( child: TextButton( child: Text('Generate Subtasks'), onPressed: () { subtaskNotifier.generateSubtasks(); }, ), ), for (String subtask in subtasks) ListTile( title: Text(subtask), trailing: IconButton( icon: Icon( Icons.delete, color: theme.colorScheme.onError, ), onPressed: () {}, ), ), if (subtasks.isEmpty) Text( "No subtasks Available", style: GoogleFonts.poppins(color: theme.colorScheme.onTertiary), ), ], ); }, error: (error, stacktrace) { return const Center(child: Text("Error occurred")); }, loading: () { return Center(child: AnimatedEmojiLoadingIndicator()); }, ); } }
问题原因
- 直接赋值FutureProvider状态不触发加载更新:
generateSubtasks中用container.read()获取FutureProvider的状态,但read只会拿到当前快照,不会监听Future的状态变化,导致加载状态无法同步到StateNotifier。 - Widget监听对象错误:
ref.watch(subtaskNotifierProvider(goalTask).notifier)拿到的是Notifier实例而非状态,状态变化时Widget不会重建,加载状态自然无法显示。
修复方案
1. 修正SubtaskNotifier的generateSubtasks方法
手动控制加载、成功、错误状态,改用WidgetRef替代ProviderContainer:
class SubtaskNotifier extends StateNotifier<AsyncValue<List<String>>> { final GoalTask goalTask; final WidgetRef ref; SubtaskNotifier(this.ref, this.goalTask) : super(AsyncValue.data([])); void generateSubtasks() async { // 先设置加载状态 state = const AsyncValue.loading(); try { // 等待FutureProvider执行完成 final subtasks = await ref.read(generateGoalTaskSubtasksControllerProvider(goalTask).future); state = AsyncValue.data(subtasks); } catch (e, stack) { state = AsyncValue.error(e, stack); } } }
2. 更新StateNotifierProvider
将ProviderContainer替换为WidgetRef传入:
final subtaskNotifierProvider = StateNotifierProvider.family<SubtaskNotifier, AsyncValue<List<String>>, GoalTask>((ref, task) { return SubtaskNotifier(ref, task); });
3. 修正Widget中的状态监听
直接监听SubtaskNotifier的状态,确保状态变化时Widget重建:
class SubtaskGenerator extends ConsumerWidget { final GoalTask goalTask; const SubtaskGenerator({Key? key, required this.goalTask}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final theme = ref.read(themeProvider); // 监听状态而非Notifier实例 final subtaskState = ref.watch(subtaskNotifierProvider(goalTask)); final subtaskNotifier = ref.read(subtaskNotifierProvider(goalTask).notifier); return subtaskState.when( data: (subtasks) { return Column( children: [ if (subtasks.isEmpty) Center( child: TextButton( child: const Text('生成子任务'), onPressed: () { subtaskNotifier.generateSubtasks(); }, ), ), // 用map简化列表生成 ...subtasks.map((subtask) => ListTile( title: Text(subtask), trailing: IconButton( icon: Icon( Icons.delete, color: theme.colorScheme.onError, ), onPressed: () {}, ), )), if (subtasks.isEmpty) Text( "暂无子任务", style: GoogleFonts.poppins(color: theme.colorScheme.onTertiary), ), ], ); }, error: (error, stacktrace) { return const Center(child: Text("生成失败,请重试")); }, loading: () { return const Center(child: AnimatedEmojiLoadingIndicator()); }, ); } }
可选优化:移除冗余的FutureProvider
如果不需要在其他地方复用该FutureProvider的逻辑,可以直接将API调用移到SubtaskNotifier中,简化代码:
class SubtaskNotifier extends StateNotifier<AsyncValue<List<String>>> { final GoalTask goalTask; final WidgetRef ref; SubtaskNotifier(this.ref, this.goalTask) : super(AsyncValue.data([])); void generateSubtasks() async { state = const AsyncValue.loading(); try { final chatAPIController = ref.read(chatAPIControllerProvider.notifier); final subtasks = await chatAPIController.generateGoalTaskSubtasksController(goalTask); ref.read(goalTaskSubtasksListProvider.notifier).updateTasks(subtasks); state = AsyncValue.data(subtasks); } catch (e, stack) { state = AsyncValue.error(e, stack); } } }
内容的提问来源于stack exchange,提问作者e.T55
相关产品推荐
相关产品推荐

