Riverpod 状态管理完整教程
目录
- [什么是 Riverpod](#什么是 Riverpod)
- [为什么选择 Riverpod](#为什么选择 Riverpod)
- 环境安装
- 核心概念
- [Provider 的类型](#Provider 的类型)
- 读取状态
- 修改状态
- 异步状态管理
- 依赖注入与覆盖
- 最佳实践
- 常见问题
什么是 Riverpod
Riverpod 是 Flutter 生态中一款编译时安全、可测试、不依赖 Widget 树的状态管理方案,由 Provider 的原作者 Remi Rousselet 设计。它解决了 Provider 在编译期无法检测、需要 BuildContext 等问题。
Riverpod 的核心设计特点:
- 编译时安全:错误的 Provider 引用会在编译期报错,而不是运行期崩溃。
- 不依赖 BuildContext:可以在任意位置(包括纯 Dart 代码)读取状态。
- 可测试:Provider 可以被轻松覆盖(override)以进行单元测试。
- 自动内存管理:Widget 不再使用时,相关的状态监听会自动释放。
为什么选择 Riverpod
| 特性 | Provider | Riverpod |
|---|---|---|
| 编译时安全 | 否 | 是 |
| 需要 BuildContext | 是 | 否 |
| 代码生成 | 可选 | 强烈推荐 |
| 自动内存管理 | 部分 | 是 |
| 与 Flutter 版本耦合 | 较紧 | 松 |
如果你正在使用 Provider 或 InheritedWidget,迁移到 Riverpod 可以显著减少因错用 context 导致的运行时错误。
环境安装
在 pubspec.yaml 中添加依赖:
yaml
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.5.1
# 如果喜欢 Hooks 风格,再加:
hooks_riverpod: ^2.5.1
flutter_hooks: ^0.20.5
然后运行:
bash
flutter pub get
启用代码生成(推荐)
Riverpod 2.x 推荐通过 build_runner 生成 Provider。开发依赖如下:
yaml
dev_dependencies:
build_runner: ^2.4.11
riverpod_generator: ^2.4.0
custom_lint: ^0.6.4
riverpod_lint: ^2.3.10
生成命令:
bash
dart run build_runner watch -d
核心概念
Riverpod 有三个核心要素:
- Provider:状态定义。
- Consumer:状态的读取者,通常是 Widget。
- WidgetRef / Ref:用于读取或修改状态的引用。
最小示例
dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 1. 定义一个 Provider
final counterProvider = StateProvider<int>((ref) => 0);
void main() {
// 2. 用 ProviderScope 包裹应用
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Riverpod Demo')),
body: const CounterView(),
),
);
}
}
class CounterView extends ConsumerWidget {
const CounterView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 3. 读取状态
final count = ref.watch(counterProvider);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () {
// 4. 修改状态
ref.read(counterProvider.notifier).state++;
},
child: const Text('Increment'),
),
],
),
);
}
}
关键说明
ProviderScope必须放在应用最外层,它是所有 Provider 的容器。ConsumerWidget替代StatelessWidget,提供WidgetRef。ref.watch(provider)用于监听状态变化并重建 Widget。ref.read(provider)用于读取一次状态,常用于事件回调中。
Provider 的类型
Riverpod 提供了多种 Provider,适用于不同场景。
1. Provider
用于提供不可变 或派生的值。
dart
final greetingProvider = Provider<String>((ref) => 'Hello Riverpod');
class GreetingView extends ConsumerWidget {
const GreetingView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final greeting = ref.watch(greetingProvider);
return Text(greeting);
}
}
2. StateProvider
用于简单的可变状态,如计数器、开关状态等。
dart
final darkModeProvider = StateProvider<bool>((ref) => false);
SwitchListTile(
title: const Text('Dark Mode'),
value: ref.watch(darkModeProvider),
onChanged: (value) => ref.read(darkModeProvider.notifier).state = value,
)
3. FutureProvider
用于封装异步操作,如 HTTP 请求。
dart
final userProvider = FutureProvider<User>((ref) async {
final response = await http.get(Uri.parse('https://api.example.com/user'));
return User.fromJson(jsonDecode(response.body));
});
class UserView extends ConsumerWidget {
const UserView({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncUser = ref.watch(userProvider);
return asyncUser.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
);
}
}
4. StreamProvider
用于监听实时数据流,如 WebSocket、Firebase 实时数据等。
dart
final messagesProvider = StreamProvider<List<Message>>((ref) {
return FirebaseFirestore.instance
.collection('messages')
.snapshots()
.map((snapshot) => snapshot.docs.map((e) => Message.fromJson(e.data())).toList());
});
5. StateNotifierProvider / AsyncNotifier
用于复杂状态逻辑。Riverpod 2.x 推荐使用 AsyncNotifier 处理异步逻辑,StateNotifier 仍可用于同步场景。
dart
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
void decrement() => state--;
}
生成的 Provider 使用方式:
dart
final count = ref.watch(counterProvider);
ref.read(counterProvider.notifier).increment();
读取状态
ref.watch
当 Provider 状态变化时,自动重建 Widget。
dart
final count = ref.watch(counterProvider);
ref.read
只读取当前值,不建立监听关系。适合在按钮点击等事件中使用。
dart
onPressed: () => ref.read(counterProvider.notifier).increment(),
ref.listen
监听状态变化并执行副作用,如弹出 Snackbar、跳转页面。
dart
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen<AsyncValue<User>>(userProvider, (previous, next) {
next.whenOrNull(
error: (error, _) => ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error.toString())),
),
);
});
return Container();
}
选择性监听
使用 select 只监听对象的部分字段,避免不必要的重建。
dart
final name = ref.watch(userProvider.select((user) => user.name));
修改状态
简单状态
dart
ref.read(counterProvider.notifier).state++;
复杂状态(StateNotifier)
dart
class TodoNotifier extends StateNotifier<List<Todo>> {
TodoNotifier() : super([]);
void add(String title) {
state = [...state, Todo(title: title, completed: false)];
}
void toggle(int index) {
state = [
for (var i = 0; i < state.length; i++)
if (i == index)
state[i].copyWith(completed: !state[i].completed)
else
state[i],
];
}
}
final todoProvider = StateNotifierProvider<TodoNotifier, List<Todo>>((ref) {
return TodoNotifier();
});
调用:
dart
ref.read(todoProvider.notifier).add('Buy milk');
异步状态管理
使用 AsyncNotifier 处理需要等待网络或数据库的场景。
dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'user_notifier.g.dart'; // 由 build_runner 生成
@riverpod
class UserNotifier extends _$UserNotifier {
@override
Future<User> build() async {
return await _fetchUser();
}
Future<User> _fetchUser() async {
final response = await http.get(Uri.parse('https://api.example.com/user'));
return User.fromJson(jsonDecode(response.body));
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(_fetchUser);
}
}
在 UI 中使用:
dart
final user = ref.watch(userNotifierProvider);
return user.when(
data: (user) => Text(user.name),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Column(
children: [
Text('加载失败: $err'),
ElevatedButton(
onPressed: () => ref.read(userNotifierProvider.notifier).refresh(),
child: const Text('重试'),
),
],
),
);
依赖注入与覆盖
Riverpod 允许在测试中覆盖 Provider,非常方便。
在测试中覆盖
dart
void main() {
testWidgets('Counter increments', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
counterProvider.overrideWith((ref) => 42),
],
child: const MyApp(),
),
);
expect(find.text('Count: 42'), findsOneWidget);
});
}
在运行时覆盖
dart
ProviderScope(
overrides: [
apiBaseUrlProvider.overrideWithValue('https://staging.example.com'),
],
child: const MyApp(),
)
最佳实践
- 优先使用代码生成 :用
@riverpod注解生成 Provider,减少手写错误。 - Widget 尽量薄:将业务逻辑放在 Notifier 中,Widget 只负责展示。
- 避免在
build中使用ref.read:除非确实只需要一次性读取。 - 使用
select精确监听:避免大对象变化导致全量重建。 - 将 Provider 放在独立文件:每个业务模块一个文件,便于维护。
- 异步操作使用
AsyncNotifier:统一处理 loading、error、data 状态。
常见问题
Q1: ref.read 和 ref.watch 有什么区别?
ref.watch:建立监听,状态变化时重建。ref.read:只读取当前值,不重建。
Q2: 为什么状态没有更新?
- 检查是否调用了
.notifier或.state。 - 确保对象是不可变的,直接修改对象字段不会触发重建。
Q3: 如何处理 Provider 之间的依赖?
dart
final userIdProvider = Provider<String>((ref) => '123');
final userProfileProvider = FutureProvider<User>((ref) async {
final userId = ref.watch(userIdProvider);
return await fetchUser(userId);
});
Q4: 与 Bloc、GetX 相比如何?
| 维度 | Riverpod | Bloc | GetX |
|---|---|---|---|
| 学习曲线 | 中 | 高 | 低 |
| 编译安全 | 高 | 中 | 低 |
| 可测试性 | 高 | 高 | 中 |
| 社区生态 | 增长快 | 成熟 | 成熟 |
结语
Riverpod 通过编译时安全和清晰的架构,让 Flutter 状态管理更加可靠。建议新项目直接使用 Riverpod 2.x 配合代码生成,老项目可以逐步迁移。掌握 Provider、StateProvider、FutureProvider、AsyncNotifier 四种核心类型,足以应对绝大多数 Flutter 应用场景。