用途
本技能定义如何在 Flutter 和 Dart 应用中正确使用 Riverpod 进行状态管理,覆盖 Provider 定义、状态生命周期、参数传递、副作用处理与测试等主题。
一、初始化
在 runApp 中直接用 ProviderScope 包裹应用,不要放在 MyApp 内部:
void main() {
runApp(const ProviderScope(child: MyApp()));
}
安装并使用 riverpod_lint 以启用 IDE 重构并强制最佳实践。
二、定义 Provider
// 函数式 provider(代码生成)
@riverpod
int example(Ref ref) => 0;
// FutureProvider(代码生成)
@riverpod
Future<List<Todo>> todos(Ref ref) async {
return ref.watch(repositoryProvider).fetchTodos();
}
// Notifier(代码生成)
@riverpod
class TodosNotifier extends _$TodosNotifier {
@override
Future<List<Todo>> build() async {
return ref.watch(repositoryProvider).fetchTodos();
}
Future<void> addTodo(Todo todo) async { ... }
}
要求:所有 provider 定义为 final 顶层变量;根据返回类型选择 Provider、FutureProvider 或 StreamProvider;访问 provider 时使用 ConsumerWidget 或 ConsumerStatefulWidget,而非 StatelessWidget/StatefulWidget。
三、使用 Ref
各方法用途:
- ref.watch:响应式监听,值变化时重建;仅在 build 阶段使用。
- ref.read:一次性访问,用于回调/Notifier 方法,不要在 build 中使用。
- ref.listen:命令式订阅,能使用 ref.watch 时优先用 watch。
- ref.onDispose:provider 状态销毁时清理。
// 在 widget 中
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final value = ref.watch(myProvider);
return Text('$value');
}
}
// 在 provider 中清理
final provider = StreamProvider<int>((ref) {
final controller = StreamController<int>();
ref.onDispose(controller.close);
return controller.stream;
});
禁止在回调、监听器或 Notifier 方法中调用 ref.watch;从 UI 调用 Notifier 方法用 ref.read(yourNotifierProvider.notifier).method();在异步回调中 await 之后使用 ref 前检查 context.mounted。
四、组合 Provider
@riverpod
Future<String> userGreeting(Ref ref) async {
final user = await ref.watch(userProvider.future);
return 'Hello, ${user.name}!';
}
使用 ref.watch(asyncProvider.future) 等待异步 provider 解析后的值。Provider 只执行一次并缓存结果,多个 widget 监听同一 provider 共享同一次计算。
五、传参(Families)
@riverpod
Future<Todo> todo(Ref ref, String id) async {
return ref.watch(repositoryProvider).fetchTodo(id);
}
// 使用
final todo = ref.watch(todoProvider('some-id'));
带参数的 provider 应始终启用 autoDispose 以防内存泄漏;多参数使用 Dart 3 records 或代码生成(天然重写 ==);避免用普通 List 或 Map 作参数(未重写 ==),改用 const 集合、records 或具备正确相等性的类;用 riverpod_lint 的 provider_parameters 规则捕获相等性错误。
六、自动销毁与状态生命周期
使用代码生成时:不再被监听后状态默认销毁,可用 keepAlive: true 退出。不使用代码生成时:状态默认保活,需用 .autoDispose 启用销毁。provider 重算时状态总是被销毁。
// 配合定时器的 keepAlive
ref.onCancel(() {
final link = ref.keepAlive();
Timer(const Duration(minutes: 5), link.close);
});
用 ref.onDispose 做清理,但不要在其中有副作用或修改 provider;用 ref.invalidate(provider) 强制销毁,provider 内部用 ref.invalidateSelf();用 ref.refresh(provider) 失效并立即读取新值,始终使用其返回值。
七、预初始化
Provider 默认惰性。要预初始化:
// 在 MyApp 或 ProviderScope 下的专用 widget 中:
Consumer(
builder: (context, ref, _) {
ref.watch(myEagerProvider); // 强制初始化
return const MyApp();
},
)
将预初始化放在公开 widget(而非 main())中以保证测试行为一致;用 AsyncValue.requireValue 直接读取数据,未就绪时抛出明确错误。
八、执行副作用
@riverpod
class TodosNotifier extends _$TodosNotifier {
Future<void> addTodo(Todo todo) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await ref.read(repositoryProvider).addTodo(todo);
return [...?state.value, todo];
});
}
}
// UI 中:
ElevatedButton(
onPressed: () => ref.read(todosNotifierProvider.notifier).addTodo(todo),
child: const Text('Add'),
)
事件处理器中用 ref.read(不是 ref.watch);副作用后通过直接设置状态、调用 ref.invalidateSelf() 或手动更新缓存来更新状态;UI 中始终处理 loading 与 error 状态;不要在 provider 构造函数或 build 方法中执行副作用。
九、Provider 观察者
class MyObserver extends ProviderObserver {
@override
void didUpdateProvider(ProviderObserverContext context, Object? previousValue, Object? newValue) {
print('[${context.provider}] updated: $previousValue → $newValue');
}
@override
void providerDidFail(ProviderObserverContext context, Object error, StackTrace stackTrace) {
// 上报到错误服务
}
}
runApp(ProviderScope(observers: [MyObserver()], child: MyApp()));
十、测试
// 单元测试
final container = ProviderContainer(
overrides: [repositoryProvider.overrideWith((_) => FakeRepository())],
);
addTearDown(container.dispose);
expect(await container.read(todosProvider.future), isNotEmpty);
// Widget 测试
await tester.pumpWidget(
ProviderScope(
overrides: [repositoryProvider.overrideWith((_) => FakeRepository())],
child: const MyApp(),
),
);
每个测试创建新的 ProviderContainer 或 ProviderScope,不在测试间共享状态;对 autoDispose provider 使用 container.listen 而非 container.read 以在测试期间保持状态存活;用 overrides 注入 mock 或 fake;优先 mock 依赖(repository)而非直接 mock Notifier;若必须 mock Notifier,应继承原始类,不要用 implements 或 with Mock;使用代码生成时将 Notifier mock 放在与 Notifier 同一文件中;在 widget 测试中用 ProviderScope.containerOf(tester.element(...)) 获取容器。
参考
- Riverpod GitHub 仓库
限制
上述规则基于 Riverpod 的代码生成写法,未提供非代码生成方式下各主题的完整对照说明;其他平台支持情况与版本要求暂未提供。