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

使用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());
      },
    );
  }
}

问题原因

  1. 直接赋值FutureProvider状态不触发加载更新:generateSubtasks中用container.read()获取FutureProvider的状态,但read只会拿到当前快照,不会监听Future的状态变化,导致加载状态无法同步到StateNotifier。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 08:48:12