关于 riverpod
面向 Flutter 和 Dart 应用开发者的 Riverpod 状态管理指南,涵盖 Provider 设计、生命周期、异步状态与测试实践。
主要功能
- 01Provider 设置与定义
- 02Ref 使用和 Provider 组合
- 03生命周期与销毁指导
- 04异步状态和副作用模式
- 05单元与组件测试
适用场景
- 01使用 Provider 组织应用状态
- 02管理异步数据与错误
- 03避免 Provider 生命周期泄漏
- 04测试 Provider 和组件
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ evanca/ flutter-ai-rules --skill riverpod安装后在 Claude Code 中输入/riverpod确认已加载。
详细介绍
Riverpod 技能
本技能介绍如何在 Flutter 和 Dart 应用中正确使用 Riverpod 进行状态管理。
1. 设置
void main() {
runApp(const ProviderScope(child: MyApp()));
}
- 直接在
runApp中使用ProviderScope包裹应用,切勿在MyApp内使用。 - 安装并使用
riverpod_lint,以启用 IDE 重构并强制执行最佳实践。
2. 定义 Provider
// Functional provider (codegen)
@riverpod
int example(Ref ref) => 0;
// FutureProvider (codegen)
@riverpod
Future<List<Todo>> todos(Ref ref) async {
return ref.watch(repositoryProvider).fetchTodos();
}
// Notifier (codegen)
@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。
3. 使用 Ref
| 方法 | 用途 |
|---|---|
ref.watch | 响应式监听——值变化时会触发重建。仅在构建阶段使用。 |
ref.read | 一次性访问——在回调/Notifier 方法中使用,不要在构建阶段使用。 |
ref.listen | 命令式订阅——尽可能优先使用 ref.watch。 |
ref.onDispose | Provider 状态销毁时执行清理。 |
// In a widget
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final value = ref.watch(myProvider);
return Text('$value');
}
}
// Cleanup in a 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。
4. 组合 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 时会共享一次计算。
5. 传递参数(Families)
@riverpod
Future<Todo> todo(Ref ref, String id) async {
return ref.watch(repositoryProvider).fetchTodo(id);
}
// Usage
final todo = ref.watch(todoProvider('some-id'));
- 对带参数的 Provider 始终启用
autoDispose,以防止内存泄漏。 - 多个参数可使用
Dart 3 records或代码生成——它们会自然地重载==。 - 避免将普通
List或Map作为参数(它们没有重载==);请使用const集合、record 或正确实现相等性判断的类。 - 使用
riverpod_lint中的provider_parameterslint 规则来发现相等性错误。
6. 自动销毁与状态生命周期
- 使用代码生成时:不再有监听者后,状态默认会被销毁。可通过
keepAlive: true选择不销毁。 - 不使用代码生成时:状态默认会保持存活。使用
.autoDispose启用销毁。 - Provider 重新计算时,状态始终会被销毁。
// keepAlive with timer
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 失效并立即读取新值——务必使用其返回值。
7. 急切初始化
Provider 默认是惰性的。若要急切初始化:
// In MyApp or a dedicated widget under ProviderScope:
Consumer(
builder: (context, ref, _) {
ref.watch(myEagerProvider); // forces initialization
return const MyApp();
},
)
- 将急切初始化放在公开 Widget 中(而不是
main()中),以确保测试行为一致。 - 使用
AsyncValue.requireValue直接读取数据,并在数据尚未就绪时清晰地抛出异常。
8. 执行副作用
@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];
});
}
}
// In UI:
ElevatedButton(
onPressed: () => ref.read(todosNotifierProvider.notifier).addTodo(todo),
child: const Text('Add'),
)
- 在事件处理器中使用
ref.read(而不是ref.watch)。 - 执行副作用后,通过以下方式更新状态:直接设置状态、调用
ref.invalidateSelf(),或手动更新缓存。 - 始终在 UI 中处理加载和错误状态。
- 不要在 Provider 构造函数或构建方法中执行副作用。
9. 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) {
// Report to error service
}
}
runApp(ProviderScope(observers: [MyObserver()], child: MyApp()));
10. 测试
// Unit test
final container = ProviderContainer(
overrides: [repositoryProvider.overrideWith((_) => FakeRepository())],
);
addTearDown(container.dispose);
expect(await container.read(todosProvider.future), isNotEmpty);
// Widget test
await tester.pumpWidget(
ProviderScope(
overrides: [repositoryProvider.overrideWith((_) => FakeRepository())],
child: const MyApp(),
),
);
- 每项测试都创建一个新的
ProviderContainer或ProviderScope——切勿在测试之间共享状态。 - 对
autoDisposeProvider,使用container.listen而不是container.read,以便在测试期间保持状态存活。 - 使用
overrides注入模拟对象或伪对象。 - 优先模拟依赖项(存储库),而不是直接模拟 Notifier。
- 如果必须模拟 Notifier,请继承原始类型;不要使用
implements或with Mock。 - 使用代码生成时,将 Notifier 模拟对象放在与 Notifier 相同的文件中。
- 在 Widget 测试中,使用
ProviderScope.containerOf(tester.element(...))获取容器。
References
评论
支持 Markdown