riverpod

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Riverpod Skill

Riverpod 使用技能

This skill defines how to correctly use Riverpod for state management in Flutter and Dart applications.

本技能定义了如何在Flutter和Dart应用中正确使用Riverpod进行状态管理。

1. Setup

1. 配置

dart
void main() {
  runApp(const ProviderScope(child: MyApp()));
}
  • Wrap your app with
    ProviderScope
    directly in
    runApp
    — never inside
    MyApp
    .
  • Install and use
    riverpod_lint
    to enable IDE refactoring and enforce best practices.

dart
void main() {
  runApp(const ProviderScope(child: MyApp()));
}
  • runApp
    中直接用
    ProviderScope
    包裹你的应用——绝对不要在
    MyApp
    内部包裹。
  • 安装并使用
    riverpod_lint
    以启用IDE重构功能并遵循最佳实践。

2. Defining Providers

2. 定义Providers

dart
// Functional provider (codegen)

int example(Ref ref) => 0;

// FutureProvider (codegen)

Future<List<Todo>> todos(Ref ref) async {
  return ref.watch(repositoryProvider).fetchTodos();
}

// Notifier (codegen)

class TodosNotifier extends _$TodosNotifier {
  
  Future<List<Todo>> build() async {
    return ref.watch(repositoryProvider).fetchTodos();
  }

  Future<void> addTodo(Todo todo) async { ... }
}
  • Define all providers as
    final
    top-level variables
    .
  • Use
    Provider
    ,
    FutureProvider
    , or
    StreamProvider
    based on the return type.
  • Use
    ConsumerWidget
    or
    ConsumerStatefulWidget
    instead of
    StatelessWidget
    /
    StatefulWidget
    when accessing providers.

dart
// Functional provider (代码生成)

int example(Ref ref) => 0;

// FutureProvider (代码生成)

Future<List<Todo>> todos(Ref ref) async {
  return ref.watch(repositoryProvider).fetchTodos();
}

// Notifier (代码生成)

class TodosNotifier extends _$TodosNotifier {
  
  Future<List<Todo>> build() async {
    return ref.watch(repositoryProvider).fetchTodos();
  }

  Future<void> addTodo(Todo todo) async { ... }
}
  • 将所有Providers定义为顶级final变量
  • 根据返回类型选择使用
    Provider
    FutureProvider
    StreamProvider
  • 当需要访问Providers时,使用
    ConsumerWidget
    ConsumerStatefulWidget
    替代
    StatelessWidget
    /
    StatefulWidget

3. Using Ref

3. 使用Ref

MethodUse for
ref.watch
Reactively listen — rebuilds when value changes. Use during build phase only.
ref.read
One-time access — use in callbacks/Notifier methods, not in build.
ref.listen
Imperative subscription — prefer
ref.watch
where possible.
ref.onDispose
Cleanup when provider state is destroyed.
dart
// In a widget
class MyWidget extends ConsumerWidget {
  
  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;
});
  • Never call
    ref.watch
    inside callbacks, listeners, or Notifier methods.
  • Use
    ref.read(yourNotifierProvider.notifier).method()
    to call Notifier methods from the UI.
  • Check
    context.mounted
    before using
    ref
    after an
    await
    in async callbacks.

方法用途
ref.watch
响应式监听——当值变化时触发重建。仅在构建阶段使用。
ref.read
一次性访问——在回调/Notifier方法中使用,不要在构建阶段使用。
ref.listen
命令式订阅——尽可能优先使用
ref.watch
ref.onDispose
当Provider状态被销毁时执行清理操作。
dart
// 在Widget中
class MyWidget extends ConsumerWidget {
  
  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
  • 使用
    ref.read(yourNotifierProvider.notifier).method()
    从UI中调用Notifier的方法。
  • 在异步回调中的
    await
    之后使用
    ref
    前,检查
    context.mounted

4. Combining Providers

4. 组合Providers

dart

Future<String> userGreeting(Ref ref) async {
  final user = await ref.watch(userProvider.future);
  return 'Hello, ${user.name}!';
}
  • Use
    ref.watch(asyncProvider.future)
    to await an async provider's resolved value.
  • Providers only execute once and cache the result — multiple widgets listening to the same provider share one computation.

dart

Future<String> userGreeting(Ref ref) async {
  final user = await ref.watch(userProvider.future);
  return 'Hello, ${user.name}!';
}
  • 使用
    ref.watch(asyncProvider.future)
    来等待异步Provider的解析值。
  • Providers仅执行一次并缓存结果——多个监听同一个Provider的Widget会共享同一个计算结果。

5. Passing Arguments (Families)

5. 传递参数(Families)

dart

Future<Todo> todo(Ref ref, String id) async {
  return ref.watch(repositoryProvider).fetchTodo(id);
}

// Usage
final todo = ref.watch(todoProvider('some-id'));
  • Always enable
    autoDispose
    for parameterized providers to prevent memory leaks.
  • Use
    Dart 3 records
    or code generation for multiple parameters — they naturally override
    ==
    .
  • Avoid passing plain
    List
    or
    Map
    as parameters (no
    ==
    override); use
    const
    collections, records, or classes with proper equality.
  • Use the
    provider_parameters
    lint rule from
    riverpod_lint
    to catch equality mistakes.

dart

Future<Todo> todo(Ref ref, String id) async {
  return ref.watch(repositoryProvider).fetchTodo(id);
}

// 使用方式
final todo = ref.watch(todoProvider('some-id'));
  • 始终为参数化Providers启用
    autoDispose
    以防止内存泄漏。
  • 对于多个参数,使用
    Dart 3 records
    或代码生成——它们会自动重写
    ==
    方法。
  • 避免传递普通的
    List
    Map
    作为参数(没有重写
    ==
    );使用
    const
    集合、records或具有正确相等性的类。
  • 使用
    riverpod_lint
    中的
    provider_parameters
    lint规则来捕获相等性错误。

6. Auto Dispose & State Lifecycle

6. 自动销毁与状态生命周期

  • With codegen: state is destroyed by default when no longer listened to. Opt out with
    keepAlive: true
    .
  • Without codegen: state is kept alive by default. Use
    .autoDispose
    to enable disposal.
  • State is always destroyed when a provider is recomputed.
dart
// keepAlive with timer
ref.onCancel(() {
  final link = ref.keepAlive();
  Timer(const Duration(minutes: 5), link.close);
});
  • Use
    ref.onDispose
    for cleanup; do not trigger side effects or modify providers inside it.
  • Use
    ref.invalidate(provider)
    to force destruction; use
    ref.invalidateSelf()
    from within the provider.
  • Use
    ref.refresh(provider)
    to invalidate and immediately read the new value — always use the return value.

  • 使用代码生成时:默认情况下,当不再被监听时状态会被销毁。可以通过
    keepAlive: true
    选择退出该机制。
  • 不使用代码生成时:默认情况下状态会被保留。使用
    .autoDispose
    启用销毁机制。
  • 当Provider被重新计算时,状态总会被销毁。
dart
// 通过计时器保持存活
ref.onCancel(() {
  final link = ref.keepAlive();
  Timer(const Duration(minutes: 5), link.close);
});
  • 使用
    ref.onDispose
    进行清理操作;不要在其中触发副作用或修改Providers。
  • 使用
    ref.invalidate(provider)
    强制销毁状态;在Provider内部使用
    ref.invalidateSelf()
  • 使用
    ref.refresh(provider)
    来销毁并立即读取新值——务必使用返回值。

7. Eager Initialization

7. 预初始化

Providers are lazy by default. To eagerly initialize:
dart
// In MyApp or a dedicated widget under ProviderScope:
Consumer(
  builder: (context, ref, _) {
    ref.watch(myEagerProvider); // forces initialization
    return const MyApp();
  },
)
  • Place eager initialization in a public widget (not
    main()
    ) for consistent test behavior.
  • Use
    AsyncValue.requireValue
    to read data directly and throw clearly if not ready.

Providers默认是懒加载的。要实现预初始化:
dart
// 在MyApp中或ProviderScope下的专用Widget中:
Consumer(
  builder: (context, ref, _) {
    ref.watch(myEagerProvider); // 强制初始化
    return const MyApp();
  },
)
  • 将预初始化放在公共Widget中(而非
    main()
    )以确保一致的测试行为。
  • 使用
    AsyncValue.requireValue
    直接读取数据,如果数据未准备好则会抛出清晰的错误。

8. Performing Side Effects

8. 执行副作用

dart

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'),
)
  • Use
    ref.read
    (not
    ref.watch
    ) in event handlers.
  • After a side effect, update state by: setting it directly, calling
    ref.invalidateSelf()
    , or manually updating the cache.
  • Always handle loading and error states in the UI.
  • Do not perform side effects in provider constructors or build methods.

dart

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中处理加载和错误状态。
  • 不要在Provider构造函数或构建方法中执行副作用。

9. Provider Observers

9. Provider 观察者

dart
class MyObserver extends ProviderObserver {
  
  void didUpdateProvider(ProviderObserverContext context, Object? previousValue, Object? newValue) {
    print('[${context.provider}] updated: $previousValue$newValue');
  }

  
  void providerDidFail(ProviderObserverContext context, Object error, StackTrace stackTrace) {
    // Report to error service
  }
}

runApp(ProviderScope(observers: [MyObserver()], child: MyApp()));

dart
class MyObserver extends ProviderObserver {
  
  void didUpdateProvider(ProviderObserverContext context, Object? previousValue, Object? newValue) {
    print('[${context.provider}] updated: $previousValue$newValue');
  }

  
  void providerDidFail(ProviderObserverContext context, Object error, StackTrace stackTrace) {
    // 向错误服务上报
  }
}

runApp(ProviderScope(observers: [MyObserver()], child: MyApp()));

10. Testing

10. 测试

dart
// 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(),
  ),
);
  • Create a new
    ProviderContainer
    or
    ProviderScope
    for each test — never share state between tests.
  • Use
    container.listen
    over
    container.read
    for
    autoDispose
    providers to keep state alive during the test.
  • Use
    overrides
    to inject mocks or fakes.
  • Prefer mocking dependencies (repositories) rather than Notifiers directly.
  • If you must mock a Notifier, subclass the original — don't use
    implements
    or
    with Mock
    .
  • Place Notifier mocks in the same file as the Notifier if using code generation.
  • Obtain the container in widget tests with
    ProviderScope.containerOf(tester.element(...))
    .

dart
// 单元测试
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
    Providers,使用
    container.listen
    而非
    container.read
    以在测试期间保持状态存活。
  • 使用
    overrides
    注入mocks或fakes。
  • 优先mock依赖项(如仓库)而非直接mock Notifiers。
  • 如果必须mock Notifier,继承原始类——不要使用
    implements
    with Mock
  • 如果使用代码生成,将Notifier mocks放在与Notifier相同的文件中。
  • 在Widget测试中,通过
    ProviderScope.containerOf(tester.element(...))
    获取container。

References

参考资料