riverpod-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Riverpod Best Practices (v3)

Riverpod最佳实践(v3)

This skill outlines the standard operating procedures for using Riverpod v3 in this project. Adherence to these practices ensures maintainability, testability, and consistency.
本技能概述了在项目中使用Riverpod v3的标准操作流程。遵循这些实践可确保代码的可维护性、可测试性和一致性。

Core Principles

核心原则

  1. Code Generation (
    @riverpod
    ):
    ALWAYS use the
    @riverpod
    annotation and
    riverpod_generator
    . Do NOT define providers manually (e.g.,
    Provider((ref) => ...)
    ).
    • Why: Reduces boilerplate, handles
      autoDispose
      logic automatically, ensures type safety, and enables easier refactoring.
  2. Modular Architecture: Organize code by feature, not by layer.
    • Structure:
      lib/modules/[feature]/providers/
      for providers,
      lib/modules/[feature]/screens/
      for UI.
  3. Unified
    Ref
    :
    Use
    Ref
    for all provider interactions. Avoid legacy types like
    WidgetRef
    inside providers (use
    Ref
    instead).
  4. AsyncValue First: Always handle Loading/Error/Data states explicitly in the UI using
    switch
    expressions.
  1. 代码生成(
    @riverpod
    ):
    务必使用
    @riverpod
    注解和
    riverpod_generator
    。不要手动定义providers(例如:
    Provider((ref) => ...)
    )。
    • 原因: 减少样板代码,自动处理
      autoDispose
      逻辑,确保类型安全,便于重构。
  2. 模块化架构:功能而非层级组织代码。
    • 结构:
      lib/modules/[feature]/providers/
      存放providers,
      lib/modules/[feature]/screens/
      存放UI。
  3. 统一使用
    Ref
    所有provider交互都使用
    Ref
    。避免在provider内部使用
    WidgetRef
    等旧类型(改用
    Ref
    )。
  4. 优先使用AsyncValue: 在UI中始终使用
    switch
    表达式显式处理加载/错误/数据状态。

Implementation Guidelines

实现指南

1. Defining Providers

1. 定义Providers

  • Class-Based (Notifier): Use for complex state requiring methods (mutations).
    dart
    
    class MyNotifier extends _$MyNotifier { ... }
  • Functional (Future/Stream): Use for simple data fetching or read-only values.
    dart
    
    Future<Data> myData(Ref ref) async { ... }
  • KeepAlive: Use
    @Riverpod(keepAlive: true)
    ONLY for global state (User, Auth, Settings). Default is
    autoDispose
    .
  • 基于类的(Notifier): 用于需要方法(变更操作)的复杂状态。
    dart
    
    class MyNotifier extends _$MyNotifier { ... }
  • 函数式(Future/Stream): 用于简单的数据获取或只读值。
    dart
    
    Future<Data> myData(Ref ref) async { ... }
  • KeepAlive: 仅对全局状态(用户、认证、设置)使用
    @Riverpod(keepAlive: true)
    。默认使用
    autoDispose

2. State Management

2. 状态管理

  • Side Effects: Perform side effects (API calls, navigation logic) in methods within the Notifier, NOT in the
    build()
    method.
  • Ref.mounted: Always check
    ref.mounted
    after an
    await
    before setting state to prevent exceptions on disposed providers.
    dart
    if (ref.mounted) state = AsyncData(data);
  • Mutations: To update lists/data, prefer fetching fresh data or optimistically updating state.
  • 副作用: 在Notifier内部的方法中执行副作用(API调用、导航逻辑),不要在
    build()
    方法中执行。
  • Ref.mounted:
    await
    之后设置状态前,务必检查
    ref.mounted
    ,以避免已销毁的provider引发异常。
    dart
    if (ref.mounted) state = AsyncData(data);
  • 变更操作: 更新列表/数据时,优先获取最新数据或乐观更新状态。

3. Consuming Providers

3. 使用Providers

  • ConsumerWidget: Extend
    ConsumerWidget
    instead of
    StatelessWidget
    .
  • ConsumerStatefulWidget: Extend
    ConsumerStatefulWidget
    if you need
    initState
    /
    dispose
    .
  • Ref.watch: Use
    ref.watch
    inside
    build()
    to rebuild on changes.
  • Ref.read: Use
    ref.read
    inside callbacks (e.g.,
    onPressed
    ) to trigger actions. NEVER use
    ref.read
    inside
    build()
    .
  • ConsumerWidget: 继承
    ConsumerWidget
    而非
    StatelessWidget
  • ConsumerStatefulWidget: 如果需要
    initState
    /
    dispose
    ,则继承
    ConsumerStatefulWidget
  • Ref.watch:
    build()
    内部使用
    ref.watch
    ,以便在状态变化时重建组件。
  • Ref.read: 在回调(如
    onPressed
    )中使用
    ref.read
    来触发操作。绝对不要
    build()
    内部使用
    ref.read

4. Naming Conventions

4. 命名规范

  • File Name:
    snake_case
    (e.g.,
    user_controller.dart
    ).
  • Class Name:
    PascalCase
    (e.g.,
    UserController
    ).
  • Provider Name (Generated):
    camelCase
    (e.g.,
    userControllerProvider
    ).
  • 文件名: 使用
    蛇形命名法(snake_case)
    (例如:
    user_controller.dart
    )。
  • 类名: 使用
    大驼峰命名法(PascalCase)
    (例如:
    UserController
    )。
  • 生成的Provider名称: 使用
    小驼峰命名法(camelCase)
    (例如:
    userControllerProvider
    )。

Resources

资源

  • Code Snippets: See references/snippets.md for templates of Notifiers, Providers, and Consumers.
  • Testing Guide: See references/testing.md for unit and widget testing patterns.
  • Async Gaps & Lifecycle: See references/async_gaps.md for critical info on
    UnmountedRefException
    and
    keepAlive
    .
  • Provider Types: See references/provider_types.md for a decision tree on which provider to use.
  • Performance: See references/performance.md for
    ref.select
    and optimization tips.
  • Architecture: See references/architecture.md for repository patterns and anti-patterns.
  • 代码片段: 查看references/snippets.md获取Notifiers、Providers和Consumers的模板。
  • 测试指南: 查看references/testing.md获取单元测试和组件测试模式。
  • 异步间隙与生命周期: 查看references/async_gaps.md获取关于
    UnmountedRefException
    keepAlive
    的重要信息。
  • Provider类型: 查看references/provider_types.md获取选择provider的决策树。
  • 性能优化: 查看references/performance.md获取
    ref.select
    和优化技巧。
  • 架构设计: 查看references/architecture.md获取仓库模式和反模式相关内容。

Migration (from v2/Manual)

迁移指南(从v2/手动实现)

If you encounter manual providers (
StateNotifierProvider
,
ChangeNotifierProvider
, etc.):
  1. Identify: Locate the logic.
  2. Convert: Rewrite as a
    @riverpod
    class or function.
  3. Replace: Update consumers to use the generated provider (e.g.,
    myProvider
    instead of
    myProvider.notifier
    for watching state).
如果遇到手动实现的providers(
StateNotifierProvider
ChangeNotifierProvider
等):
  1. 识别: 定位相关逻辑。
  2. 转换: 重写为
    @riverpod
    类或函数。
  3. 替换: 更新使用者以使用生成的provider(例如,观察状态时使用
    myProvider
    而非
    myProvider.notifier
    )。