Riverpod 完全指南:从入门到精通
一篇让你看完就能上手 Riverpod 的文章
目录
一、Riverpod 是什么
Riverpod 是由 Remi Rousselet 开发的响应式缓存与数据绑定框架,是 Provider 的继任者。它的名字是 Provider 的“变位词”(anagram),定位是一个 “响应式缓存和数据绑定框架” 。
简单来说,Riverpod 的核心工作方式是:Provider 是“带缓存的函数”,通过 ref 组合依赖、自动失效;UI 通过 ref.watch 订阅,状态变化时自动重建。
截至 2026 年,Riverpod 已成为官方推荐、社区首选的状态管理方案,GitHub Stars 超 28k,被阿里、字节、腾讯等大厂广泛采用。
二、为什么选择 Riverpod
2.1 核心优势
| 特性 | 说明 |
|---|---|
| 编译期安全 | 不依赖 BuildContext,可在 initState、测试、纯 Dart 中使用 |
| 响应式缓存 | Provider 即“带缓存的函数”,自动管理生命周期 |
| 依赖自动追踪 | ref.watch 建立依赖图,依赖变化自动失效并重建 |
| 异步原生支持 | FutureProvider、AsyncNotifier、AsyncValue 内置 |
| 可测试性 | ProviderScope + overrides 轻松 mock |
| 自动释放 | autoDispose 无监听时自动 dispose |
| 参数化 | family 支持带参数的 Provider |
2.2 解决了什么痛点
在 Flutter 项目中,状态管理常见痛点包括:
- 页面状态散落在 StatefulWidget 中,后期难维护
- 网络请求、loading、error、empty 状态处理重复
- 多页面共享状态困难
- Controller、Repository、Service 依赖关系混乱
- 单元测试时很难 mock 数据源
- 页面销毁后状态释放不清晰
Riverpod 通过不依赖 BuildContext、天然支持分层架构、AsyncValue 统一管理 loading/error/data、支持 overrides 测试、autoDispose 自动释放等机制,一一解决了这些问题。
三、与其他方案的对比
3.1 横向对比
| 维度 | Provider | Riverpod | Bloc | GetX |
|---|---|---|---|---|
| 学习曲线 | 低 | 中 | 高 | 低-中 |
| 可测试性 | 良好 | 优秀 | 优秀 | 一般 |
| 样板代码 | 低 | 低 | 高 | 低 |
| 编译时安全 | 一般 | 优秀 | 优秀 | 一般 |
| 社区规模 | 大 | 大 | 大 | 中 |
| 异步类型安全 | 一般 | 优秀(AsyncValue) | 良好 | 一般 |
3.2 各方案特点
Provider:Flutter 官方推荐的轻量级方案,底层基于 InheritedWidget,学习成本低。但复杂项目下 Provider 嵌套容易形成“金字塔地狱”,且依赖 BuildContext。
Bloc:企业级的事件驱动架构,强制单向数据流,适合大型团队。但样板代码多,一个小功能就要写 Event + State + Bloc 三个文件。
GetX:瑞士军刀式全能选手,API 极简,内置路由和依赖注入。但封装过深、存在隐式全局依赖,难以测试。
Riverpod:编译时安全、无需 Context、组合能力强、测试友好,代表 Flutter 状态管理的未来方向。
四、核心概念
4.1 四大核心组件
Riverpod 的核心由四个概念组成:
- Provider:状态的描述,本质是“带缓存的函数”,顶层声明,不可变
- ProviderContainer:存储 Provider 实际状态,Flutter 中由 ProviderScope 创建
- Ref:Provider 之间、Provider 与 UI 之间的桥梁
- Consumer:ConsumerWidget、Consumer、ConsumerStatefulWidget 等,提供 WidgetRef
4.2 ProviderScope
使用 Riverpod 的第一步,是在 Flutter 应用根节点包裹 ProviderScope:
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
ProviderScope 可以理解为 Riverpod 的状态容器,所有 Provider 的状态都由它管理。
4.3 工作原理简述
Riverpod 的核心机制可以概括为:
- Provider 是纯函数描述:Provider 本身不可变,只是“如何计算值”的描述,不持有状态
- ProviderContainer 存储状态:实际的状态存储在容器中
- ref 建立连接:通过 ref 在 Provider 之间、Provider 与 UI 之间建立连接
当一个 widget 或 provider 请求一个 provider 的值时,Riverpod 在最近的 ProviderScope widget 中查找该 provider 的状态。
4.4 Provider 生命周期
所有 provider 都会经历相同的状态周期:
- 未初始化/已销毁:不占用任何内存
- 活动中:状态可被读取和监听,依赖关系建立
- 暂停:当不再被监听时进入暂停状态,减少计算负担
当读取、监听或观察未初始化的 provider 时,会创建其状态并进入活动状态。
五、Provider 类型详解
5.1 类型总览
Riverpod 3.0 将旧版 provider(StateProvider、FutureProvider 等)标记为遗留(legacy),推荐使用基于 Notifier 的新类型。
推荐使用的 Provider 类型:
| Provider 类型 | 描述 | 适用场景 |
|---|---|---|
| Provider | 只读同步,返回固定或计算值 | 常量配置、依赖注入、简单计算 |
| NotifierProvider | 可变同步状态 | 计数器、表单输入、简单 UI 状态 |
| AsyncNotifierProvider | 可变异步状态,内置 loading/error/data | API 调用、数据库查询 |
| StreamNotifierProvider | 可变流式状态 | WebSocket、实时数据流 |
遗留类型(仅迁移参考) :
-
StateProvider→ 迁移到NotifierProvider -
FutureProvider→ 迁移到AsyncNotifierProvider -
StreamProvider→ 迁移到StreamNotifierProvider -
StateNotifierProvider→ 迁移到NotifierProvider
5.2 选择决策流程
选择 provider 时,按以下流程决策:
- 数据是同步还是异步? → 同步用 NotifierProvider,异步用 AsyncNotifierProvider
- 数据是只读还是可变? → 只读用 Provider,可变用 Notifier
- 是否需要复杂业务逻辑? → 使用 Notifier 类封装
-
是否需要自动释放或参数化? → 使用
.autoDispose或.family修饰符
5.3 代码示例
Provider(只读依赖注入)
final apiClientProvider = Provider<ApiClient>((ref) {
return ApiClient();
});
适合存放不会主动变化的对象,如 Repository、Service、配置项等。
NotifierProvider(同步可变状态)
final cartProvider = NotifierProvider<CartNotifier, List<String>>(
CartNotifier.new,
);
class CartNotifier extends Notifier<List<String>> {
@override
List<String> build() => [];
void add(String item) {
state = [...state, item];
}
void remove(String item) {
state = state.where((i) => i != item).toList();
}
}
适合购物车、表单状态、筛选条件等场景。
AsyncNotifierProvider(异步可变状态)
@riverpod
class UserProfile extends _$UserProfile {
@override
Future<User> build() async {
final api = ref.watch(apiClientProvider);
return await api.fetchUser();
}
Future<void> updateName(String name) async {
state = const AsyncValue.loading();
try {
final api = ref.watch(apiClientProvider);
final updated = await api.updateUserName(name);
state = AsyncValue.data(updated);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
}
使用 AsyncNotifier 时,ref.watch 会返回 AsyncValue,内置 loading、error、data 三种状态。
六、ref 的三种用法
ref 是 Riverpod 中连接 Provider 和 UI 的桥梁,提供了三种核心方法。
6.1 ref.watch —— 声明式监听
ref.watch 用于在 build 方法中声明式地监听 provider 状态,当状态变化时自动重建 UI。
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// watch 会建立响应式关系,状态变化时自动重建
final count = ref.watch(counterProvider);
return Text('Count: $count');
}
}
关键规则:ref.watch 只能在 build 方法的根部使用,不能在异步回调(如 onPressed)中调用。
6.2 ref.read —— 一次性读取
ref.read 用于一次性读取 provider 的当前状态,不建立监听关系。
// 在按钮点击事件中调用方法
onPressed: () {
// read 只读一次,不监听变化
ref.read(counterProvider.notifier).increment();
}
使用场景:事件处理中调用 Notifier 的方法、不需要 UI 响应的场景。
注意:官方建议尽可能使用 ref.watch 而非 ref.read,因为 ref.watch 更安全。
6.3 ref.listen —— 副作用监听
ref.listen 用于监听状态变化并执行副作用(如导航、弹窗、日志等),不会触发 UI 重建。
ref.listen(authProvider, (previous, next) {
if (previous != next && next.isLoggedIn) {
// 用户登录后跳转
Navigator.pushNamed(context, '/home');
}
});
七、高级特性
7.1 代码生成(@riverpod)
Riverpod 官方推荐使用 @riverpod 注解配合代码生成,可以大幅减少样板代码。
安装依赖:
dependencies:
flutter_riverpod: ^3.3.1
riverpod_annotation: ^4.0.2
dev_dependencies:
build_runner: ^2.14.0
riverpod_generator: ^4.0.2
使用示例:
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter.g.dart';
@riverpod
int counter(CounterRef ref) => 0;
只需这样简单的注解,Riverpod 就会自动生成 counterProvider。
运行代码生成:
dart run build_runner watch --delete-conflicting-outputs
7.2 family(参数化 Provider)
family 允许根据参数创建多个 Provider 实例,适用于需要参数的场景。
@riverpod
Future<User> user(UserRef ref, {required String id}) async {
final api = ref.watch(apiClientProvider);
return await api.fetchUser(id);
}
// 使用时传入参数
final user = ref.watch(userProvider(id: '123'));
注意:参数必须具有一致的 hashCode 和 ==,建议使用原始类型(bool/int/double/String)或不可变对象。
7.3 autoDispose(自动释放)
autoDispose 让 provider 在不再被监听时自动销毁状态,释放内存。
使用代码生成时,autoDispose 默认开启:
@riverpod
@AutoDispose() // 默认就是 autoDispose
Future<List<Post>> posts(PostsRef ref) async {
final api = ref.watch(apiClientProvider);
return await api.fetchPosts();
}
如需保持状态存活,可以使用 keepAlive。
7.4 select(精细化重建)
select 允许只监听对象的特定属性,减少不必要的 UI 重建。
// 只监听 user 的 name 属性,其他属性变化不会触发重建
final userName = ref.watch(userProvider.select((user) => user.name));
八、实战案例
8.1 计数器(最简示例)
定义 Provider:
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
void decrement() => state--;
}
使用 Provider:
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: Text('+'),
),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).decrement(),
child: Text('-'),
),
],
);
}
}
8.2 TodoList(带异步操作的复杂状态)
定义 Todo 模型和 Notifier:
class Todo {
final String id;
final String title;
final bool completed;
Todo({required this.id, required this.title, this.completed = false});
}
@riverpod
class TodoList extends _$TodoList {
@override
List<Todo> build() => [];
void add(String title) {
state = [
...state,
Todo(id: DateTime.now().toString(), title: title),
];
}
void toggle(String id) {
state = state.map((todo) {
if (todo.id == id) {
return Todo(id: todo.id, title: todo.title, completed: !todo.completed);
}
return todo;
}).toList();
}
void remove(String id) {
state = state.where((todo) => todo.id != id).toList();
}
}
// 派生状态:已完成数量
@riverpod
int completedCount(CompletedCountRef ref) {
final todos = ref.watch(todoListProvider);
return todos.where((todo) => todo.completed).length;
}
UI 使用:
class TodoPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoListProvider);
final completed = ref.watch(completedCountProvider);
return Column(
children: [
Text('已完成: $completed / ${todos.length}'),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (ctx, i) {
final todo = todos[i];
return ListTile(
title: Text(todo.title),
leading: Checkbox(
value: todo.completed,
onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => ref.read(todoListProvider.notifier).remove(todo.id),
),
);
},
),
),
],
);
}
}
8.3 异步 API 调用
定义异步 Provider:
@riverpod
class UserList extends _$UserList {
@override
Future<List<User>> build() async {
final api = ref.watch(apiClientProvider);
return await api.fetchUsers();
}
Future<void> refresh() async {
// 重新执行 build
state = await build();
}
}
UI 中使用 AsyncValue:
class UserListPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(userListProvider);
return usersAsync.when(
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (ctx, i) => Text(users[i].name),
),
loading: () => const CircularProgressIndicator(),
error: (err, st) => Text('Error: $err'),
);
}
}
AsyncValue 的 when 方法统一处理了 loading、data、error 三种状态。
8.4 依赖注入与组合
Riverpod 的 ref.watch 天然支持 Provider 之间的依赖组合:
@riverpod
ApiClient apiClient(ApiClientRef ref) {
return ApiClient();
}
@riverpod
class UserRepository extends _$UserRepository {
@override
Future<List<User>> build() async {
final api = ref.watch(apiClientProvider);
return await api.fetchUsers();
}
}
@riverpod
class DashboardData extends _$DashboardData {
@override
Future<DashboardData> build() async {
// 组合多个 Provider
final users = await ref.watch(userRepositoryProvider.future);
final stats = await ref.watch(statsProvider.future);
return DashboardData(users: users, stats: stats);
}
}
8.5 单元测试
Riverpod 的测试非常简洁,无需 Widget 树:
test('TodoList 添加和切换', () {
final container = ProviderContainer();
addTearDown(container.dispose);
// 添加 todo
container.read(todoListProvider.notifier).add('任务A');
expect(container.read(todoListProvider).length, 1);
// 切换完成状态
final id = container.read(todoListProvider).first.id;
container.read(todoListProvider.notifier).toggle(id);
expect(container.read(completedCountProvider), 1);
});
test('Mock 依赖做测试', () {
final container = ProviderContainer(
overrides: [
// 替换真实 Repository 为假的
userRepositoryProvider.overrideWithValue(FakeUserRepository()),
],
);
addTearDown(container.dispose);
// 测试逻辑...
});
核心优势:ProviderContainer 在纯 Dart 环境下工作,不需要 pumpWidget,测试跑得快;overrides 让你能替换任何一层依赖。
九、最佳实践
9.1 Provider 定义规范
- Provider 应声明为
final全局常量 - 使用
@riverpod注解配合代码生成,而非手动编写 Provider - 按功能模块组织 Provider 文件
9.2 ref 使用规范
| 方法 | 使用场景 | 注意事项 |
|---|---|---|
watch |
build 方法中声明式监听 | 只能在 build 方法根部使用 |
read |
事件处理中读取状态 | 避免在 build 中用于渲染 |
listen |
状态变化时执行副作用 | 不触发 UI 重建 |
9.3 状态管理分层
推荐按以下层次组织状态:
- UI State:页面级临时状态(表单输入、弹窗显示等)
- Domain State:业务领域状态(用户信息、购物车等)
- Cache State:缓存数据(API 响应缓存等)
9.4 内存管理
- 为临时状态(如详情页数据)开启
autoDispose - 使用
family时务必开启autoDispose,避免参数组合过多导致内存泄漏 - 在
TearDown中 dispose 测试用的ProviderContainer
9.5 项目结构
推荐按功能模块组织:
lib/
├── core/ # 共享资源(entities, services, utils)
├── features/ # 功能模块
│ ├── auth/
│ │ ├── providers/ # Provider 定义
│ │ ├── models/ # 数据模型
│ │ └── pages/ # UI 页面
│ └── home/
│ └── ...
└── main.dart
十、总结
Riverpod 作为 Flutter 生态中最先进的状态管理方案之一,其核心设计理念可以总结为:
Provider 是“带缓存的函数”,通过 ref 组合依赖、自动失效;UI 通过 ref.watch 订阅,状态变化时自动重建。
关键 takeaways
- 不依赖 BuildContext:可在任何地方使用,包括纯 Dart 类、测试中
- 编译时安全:Provider 作为常量,拼写错误直接报错
- 响应式缓存:自动管理生命周期,无监听时自动释放
- 异步原生支持:AsyncNotifier + AsyncValue 统一处理 loading/error/data
- 测试友好:ProviderContainer + overrides 轻松 mock
版本提示
- 当前最新稳定版本:
flutter_riverpod: ^3.3.1 - Riverpod 3.0 推荐使用 Notifier/AsyncNotifier,旧版 Provider 类型已标记为遗留
- 官方推荐使用
@riverpod注解 + 代码生成
学习路径建议
- 入门:掌握 ProviderScope、ConsumerWidget、ref.watch/read
- 进阶:学习 Notifier/AsyncNotifier、AsyncValue 的 when 方法
- 高级:掌握 family、autoDispose、代码生成、单元测试
掌握 Riverpod,你就掌握了 Flutter 状态管理的未来方向。