mcpField
R
riverpod

riverpod

github.com/evanca开发663284 次安装3 周前更新
代码仓库

关于 riverpod

面向 Flutter 和 Dart 应用开发者的 Riverpod 状态管理指南,涵盖 Provider 设计、生命周期、异步状态与测试实践。

主要功能

  1. 01Provider 设置与定义
  2. 02Ref 使用和 Provider 组合
  3. 03生命周期与销毁指导
  4. 04异步状态和副作用模式
  5. 05单元与组件测试

适用场景

  1. 01使用 Provider 组织应用状态
  2. 02管理异步数据与错误
  3. 03避免 Provider 生命周期泄漏
  4. 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.onDisposeProvider 状态销毁时执行清理。
// 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_parameters lint 规则来发现相等性错误。

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——切勿在测试之间共享状态。
  • 对 autoDispose Provider,使用 container.listen 而不是 container.read,以便在测试期间保持状态存活。
  • 使用 overrides 注入模拟对象或伪对象。
  • 优先模拟依赖项(存储库),而不是直接模拟 Notifier。
  • 如果必须模拟 Notifier,请继承原始类型;不要使用 implements 或 with Mock。
  • 使用代码生成时,将 Notifier 模拟对象放在与 Notifier 相同的文件中。
  • 在 Widget 测试中,使用 ProviderScope.containerOf(tester.element(...)) 获取容器。

References

评论

支持 Markdown