riverpod-from-provider

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Riverpod — Migrating from Provider

Riverpod — 从Provider迁移

Motivation

迁移动机

Riverpod was created as a successor to Provider, addressing InheritedWidget limitations:
  • Same type: Provider can't have two
    Provider<Item>
    in the tree (only the nearest is found). Riverpod has no such limit; providers are identified by variable, not type.
  • Combining providers: ProxyProvider is tedious and error-prone. In Riverpod, use ref.watch inside a provider to depend on others; composition is straightforward.
  • AsyncValue: Riverpod can expose previous data while loading (e.g. show old list + loading indicator). Provider doesn't offer this cleanly.
  • Safety: Provider can throw
    ProviderNotFoundException
    at runtime. Riverpod avoids this by design.
  • Disposal: Provider can't react when consumers stop listening; scoping is tricky. Riverpod offers autoDispose and ref.keepAlive for clear lifecycle and caching.
  • Parameters: Riverpod's .family gives type-safe, parameterized providers with per-parameter state; with autoDispose, state is disposed when unused. Equivalent in Provider is impractical.
  • Testing: With Provider you must re-define providers per test. With Riverpod, override with overrides to mock.
  • Side effects: Riverpod offers ref.listen for reacting to changes (e.g. navigation, snackbars). Provider has no built-in equivalent.
The main API change: use ConsumerWidget (and WidgetRef) instead of StatelessWidget, and ref.watch / ref.read instead of context.watch / context.read.

Riverpod作为Provider的继任者被开发,旨在解决InheritedWidget的局限性:
  • 同类型限制: Provider中无法在组件树中存在两个
    Provider<Item>
    (只会找到最近的那个)。Riverpod无此限制;provider通过变量而非类型来标识。
  • Provider组合: ProxyProvider繁琐且容易出错。在Riverpod中,只需在provider内部使用ref.watch来依赖其他provider,组合过程简单直接。
  • AsyncValue: Riverpod可以在加载时保留旧数据(例如显示旧列表+加载指示器)。Provider无法实现如此简洁的效果。
  • 安全性: Provider可能在运行时抛出
    ProviderNotFoundException
    。Riverpod从设计上避免了这一问题。
  • 资源销毁: Provider无法感知消费者停止监听的时机,作用域控制难度大。Riverpod提供autoDisposeref.keepAlive来实现清晰的生命周期与缓存管理。
  • 参数化: Riverpod的**.family**支持类型安全的参数化provider,每个参数对应独立状态;结合autoDispose,未被使用的状态会被销毁。Provider中实现同等功能非常不切实际。
  • 测试: 使用Provider时必须为每个测试重新定义provider。使用Riverpod时,只需通过overrides覆盖来模拟依赖。
  • 副作用处理: Riverpod提供ref.listen来响应状态变化(例如导航、提示框)。Provider没有内置的等效功能。
主要API变化:使用ConsumerWidget(及WidgetRef)替代StatelessWidget,使用ref.watch / ref.read替代context.watch / context.read

Quickstart

快速开始

  • Read the Riverpod getting started guide (riverpod-getting-started) and try a small example.
  • Migrate incrementally. You can run Provider and Riverpod side by side (use import aliases if needed).
  • 阅读Riverpod入门指南(riverpod-getting-started)并尝试简单示例。
  • 增量迁移。你可以同时运行Provider和Riverpod(必要时使用导入别名)。

Start with ChangeNotifierProvider

从ChangeNotifierProvider开始

Keep existing ChangeNotifier classes and wrap them in Riverpod's ChangeNotifierProvider:
dart
final myNotifierProvider = ChangeNotifierProvider<MyNotifier>((ref) => MyNotifier());
Use ProviderScope at the root. Replace context.watch with ref.watch where this provider is used (e.g. in a ConsumerWidget). No need to convert every ChangeNotifier to a Notifier immediately.
保留现有的ChangeNotifier类,将其包装在Riverpod的ChangeNotifierProvider中:
dart
final myNotifierProvider = ChangeNotifierProvider<MyNotifier>((ref) => MyNotifier());
在根节点使用ProviderScope。在使用该provider的位置(例如ConsumerWidget中)将context.watch替换为ref.watch。无需立即将所有ChangeNotifier转换为Notifier。

Start with leaves

从无依赖provider开始

Migrate providers that have no dependencies first (the "leaves"), then those that depend on them. Avoid migrating ProxyProviders until their dependencies are migrated.
先迁移没有依赖的provider(即“叶子节点”),再迁移依赖它们的provider。在依赖的provider完成迁移前,避免迁移ProxyProviders。

One provider at a time

逐个迁移provider

Migrate and test one provider at a time. Full migration of a ChangeNotifier means: (1) convert to Notifier + NotifierProvider, (2) replace every context.watch for it with ref.watch.
逐个迁移并测试provider。完整迁移一个ChangeNotifier意味着:(1) 将其转换为Notifier + NotifierProvider;(2) 将所有针对它的context.watch替换为ref.watch

ProxyProvider → ref.watch

ProxyProvider → ref.watch

In Riverpod, combining providers is done with ref.watch inside another provider:
dart
final labelProvider = Provider<String>((ref) {
  final userIdNotifier = ref.watch(userIdNotifierProvider);
  return 'The user ID is ${userIdNotifier.userId}';
});
For stateful combined objects (like ChangeNotifierProxyProvider), use ref.listen in the provider to react to another provider and update your notifier.
在Riverpod中,provider组合通过在另一个provider内部使用ref.watch实现:
dart
final labelProvider = Provider<String>((ref) {
  final userIdNotifier = ref.watch(userIdNotifierProvider);
  return 'The user ID is ${userIdNotifier.userId}';
});
对于有状态的组合对象(例如ChangeNotifierProxyProvider),在provider中使用ref.listen来响应其他provider的变化并更新你的notifier。

Eager initialization

预初始化

Riverpod providers are lazy. To warm data at startup, watch the provider at the root (e.g. in a Consumer under ProviderScope that returns your app as child). See riverpod-eager-initialization.
Riverpod的provider是懒加载的。要在启动时预加载数据,可在根节点监听该provider(例如在ProviderScope下的Consumer中返回应用作为子组件)。详情见riverpod-eager-initialization。

Code generation

代码生成

Code gen doesn't generate ChangeNotifierProvider. You can use a small extension (listenAndDisposeChangeNotifier) to expose ChangeNotifier with @riverpod during migration; once you switch to Notifier, remove the extension. See the official quickstart for the snippet.

代码生成不会生成ChangeNotifierProvider。迁移期间你可以使用一个小扩展(listenAndDisposeChangeNotifier)通过@riverpod暴露ChangeNotifier;切换到Notifier后移除该扩展。代码片段见官方快速入门指南。

Provider vs Riverpod

Provider vs Riverpod

Defining providers

定义provider

  • Provider: Providers are widgets (e.g. inside MultiProvider).
  • Riverpod: Providers are top-level final variables. No widget tree for definitions. Add ProviderScope at the root of the app.
  • Provider: Provider是组件(例如在MultiProvider内部)。
  • Riverpod: Provider是顶层final变量。无需通过组件树定义。在应用根节点添加ProviderScope

Reading

读取数据

  • Provider:
    context.watch<T>()
    ,
    context.read<T>()
    ,
    context.select<T,R>(...)
    .
  • Riverpod: Use ConsumerWidget (or Consumer) to get WidgetRef ref; then ref.watch(provider), ref.read(provider), ref.watch(provider.select(...)).
  • Use watch in build, read in event handlers. Same mental model as Provider.
  • Provider:
    context.watch<T>()
    ,
    context.read<T>()
    ,
    context.select<T,R>(...)
    .
  • Riverpod: 使用ConsumerWidget(或Consumer)获取WidgetRef ref;然后调用ref.watch(provider), ref.read(provider), ref.watch(provider.select(...)).
  • 在build方法中使用watch,在事件处理器中使用read。与Provider的思维模型一致。

Consumer

Consumer组件

Riverpod has Consumer with
(context, ref, child)
. No need for Consumer2, Consumer3, etc.: just multiple ref.watch calls in one builder.
Riverpod的Consumer使用
(context, ref, child)
。无需Consumer2、Consumer3等:只需在一个builder中多次调用ref.watch即可。

Scoping vs family + autoDispose

作用域 vs family + autoDispose

Provider uses scoping to destroy state or have per-page state. In Riverpod:
  • autoDispose destroys state when there are no listeners (ref.onCancel / ref.onDispose).
  • .family gives parameterized providers (one state per parameter). Use with autoDispose to avoid unbounded cache.
So: use .family for "state per X" and .autoDispose for "destroy when unused" instead of scoping.
See riverpod-getting-started, riverpod-providers, riverpod-family, and riverpod-auto-dispose for details.
Provider使用作用域来销毁状态或实现每页独立状态。在Riverpod中:
  • autoDispose会在没有监听器时销毁状态(通过ref.onCancel / ref.onDispose)。
  • .family提供参数化provider(每个参数对应一个独立状态)。结合autoDispose使用可避免无限缓存。
因此:使用**.family实现“按X划分状态”,使用.autoDispose**实现“未使用时销毁”,替代作用域的功能。
详情见riverpod-getting-started、riverpod-providers、riverpod-family及riverpod-auto-dispose。