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

Flutter/Riverpod:如何结合Provider实现数据列表的获取与状态维护?

关于RiverPod 2.0中结合FutureProvider与StateNotifierProvider实现CRUD的问题解答

1. 是否需要使用两个不同的Provider?

完全正确,这是Riverpod中处理这类场景的合理方案。两者的职责天然互补:

  • FutureProvider:专注于异步数据的单次/刷新式加载,适合处理初始数据获取(比如从API、本地数据库拉取列表),它的状态会明确区分加载中、成功、失败三种状态,方便UI层处理对应的界面逻辑。
  • StateNotifierProvider:负责维护可变的、可操作的状态,支持CRUD这类需要修改状态的操作,通过StateNotifier中的方法封装增删改查的业务逻辑,让状态变更可追踪、可测试。

分开使用能让代码职责更清晰,符合单一职责原则,避免把异步加载和状态修改的逻辑混在一起导致代码臃肿。

2. FutureProvider加载数据后推送至StateNotifierProvider是否可行?

这种做法完全可行,而且是实现初始数据填充的常用方式,下面提供两种具体实现思路:

方式一:UI层监听FutureProvider状态,手动触发StateNotifier初始化

// 定义数据模型
class Todo {
  final String id;
  final String title;
  Todo({required this.id, required this.title});
}

// 模拟异步加载数据的FutureProvider
final todoListFutureProvider = FutureProvider<List<Todo>>((ref) async {
  // 模拟API请求延迟
  await Future.delayed(const Duration(seconds: 2));
  return [
    Todo(id: '1', title: '初始任务1'),
    Todo(id: '2', title: '初始任务2'),
  ];
});

// 定义StateNotifier,封装CRUD逻辑
class TodoListNotifier extends StateNotifier<List<Todo>> {
  TodoListNotifier() : super([]);

  // 初始化列表
  void initialize(List<Todo> todos) {
    state = todos;
  }

  // 新增任务
  void addTodo(Todo todo) {
    state = [...state, todo];
  }

  // 删除任务
  void removeTodo(String id) {
    state = state.where((todo) => todo.id != id).toList();
  }
}

// StateNotifierProvider
final todoListNotifierProvider = StateNotifierProvider<TodoListNotifier, List<Todo>>((ref) {
  return TodoListNotifier();
});

// UI层使用示例
class TodoListView extends ConsumerWidget {
  const TodoListView({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听FutureProvider的状态
    final futureState = ref.watch(todoListFutureProvider);

    return futureState.when(
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (error, stack) => Center(child: Text('加载失败: $error')),
      data: (todos) {
        // 数据加载完成后,初始化StateNotifier的状态(仅执行一次可通过listen优化)
        ref.read(todoListNotifierProvider.notifier).initialize(todos);
        
        // 从StateNotifierProvider读取状态,用于展示和后续CRUD
        final currentTodos = ref.watch(todoListNotifierProvider);
        
        return ListView.builder(
          itemCount: currentTodos.length,
          itemBuilder: (context, index) {
            final todo = currentTodos[index];
            return ListTile(
              title: Text(todo.title),
              trailing: IconButton(
                icon: const Icon(Icons.delete),
                onPressed: () {
                  ref.read(todoListNotifierProvider.notifier).removeTodo(todo.id);
                },
              ),
            );
          },
        );
      },
    );
  }
}

方式二:在StateNotifier内部监听FutureProvider自动同步

如果你希望初始数据的同步逻辑不放在UI层,可以让StateNotifier通过ref监听FutureProvider,自动同步数据:

// 修改StateNotifier的创建方式,传入ref
class TodoListNotifier extends StateNotifier<List<Todo>> {
  TodoListNotifier(this.ref) : super([]) {
    // 监听FutureProvider,当数据加载完成时自动初始化
    ref.listen<List<Todo>>(todoListFutureProvider, (previous, next) {
      if (next != null) {
        state = next;
      }
    });
  }

  final Ref ref;

  // CRUD方法不变...
}

// 修改StateNotifierProvider的定义
final todoListNotifierProvider = StateNotifierProvider<TodoListNotifier, List<Todo>>((ref) {
  return TodoListNotifier(ref);
});

这种方式适合把数据同步逻辑封装在业务层,UI层只需要处理状态展示和操作触发即可。

注意事项

  • 避免重复初始化:如果使用UI层手动触发的方式,建议用ref.listen替代ref.watch,只在FutureProvider的数据首次加载完成时执行一次初始化,防止UI重建时重复调用。
  • 错误处理:FutureProvider的错误状态要在UI层妥善处理,避免错误数据流入StateNotifier。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 00:35:19