riverpod-from-provider について
Flutter の状態管理を Provider から Riverpod へ移行する開発者向けのガイドです。段階的な移行方法や API の変更点、Provider の一般的なパターンの置き換え方を説明します。
主な機能
- 01段階的な移行ガイド
- 02Provider と Riverpod の併用
- 03ChangeNotifier の移行手順
- 04ref.watch による Provider の合成
- 05パラメーター化と自動破棄状態
- 06Provider のオーバーライドによるテスト
活用シーン
- 01Provider アプリの段階的な移行
- 02ProxyProvider の依存関係の置き換え
- 03パラメーターごとの状態管理
- 04テストでの Provider のモック化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ serverpod/ skills-registry --skill riverpod-from-providerインストール後、Claude Code で/riverpod-from-providerを入力して読み込まれたか確認します。
詳細
Riverpod — Provider からの移行
動機
Riverpod は Provider の後継として作られ、InheritedWidget の制限に対処しています。
- 同じ型: Provider ではツリー内に
Provider<Item>を 2 つ配置できません(最も近いものだけが見つかります)。Riverpod にはそのような制限はなく、プロバイダーは型ではなく変数によって識別されます。 - プロバイダーの結合: ProxyProvider は手間がかかり、エラーが起きやすいです。Riverpod では、プロバイダー内で ref.watch を使って他のプロバイダーに依存します。構成はシンプルです。
- AsyncValue: Riverpod では読み込み中も以前のデータを公開できます(例:古いリストと読み込みインジケーターを表示)。Provider では、これを簡潔に実現できません。
- 安全性: Provider では実行時に
ProviderNotFoundExceptionがスローされることがあります。Riverpod では設計上、この問題が起きません。 - 破棄: Provider では、利用側がリッスンを停止したときに対応できません。また、スコープの管理も困難です。Riverpod には、ライフサイクルとキャッシュを明確に管理できる autoDispose と ref.keepAlive があります。
- パラメーター: Riverpod の .family では、型安全なパラメーター付きプロバイダーと、パラメーターごとの状態を利用できます。autoDispose を使えば、使用されなくなった状態は破棄されます。Provider で同等のことを実現するのは現実的ではありません。
- テスト: Provider ではテストごとにプロバイダーを再定義する必要があります。Riverpod では overrides を使ってモックに置き換えられます。
- 副作用: Riverpod には、変更への反応(例:画面遷移、スナックバーの表示)に使える ref.listen があります。Provider には組み込みの同等機能がありません。
主な API の変更点は、StatelessWidget の代わりに ConsumerWidget(および WidgetRef)を使い、context.watch/context.read の代わりに ref.watch/ref.read を使うことです。
クイックスタート
- Riverpod のスタートガイド(riverpod-getting-started)を読み、小さなサンプルを試してください。
- 段階的に移行してください。Provider と Riverpod は並行して実行できます(必要に応じてインポートのエイリアスを使ってください)。
ChangeNotifierProvider から始める
既存の ChangeNotifier クラスはそのままにして、Riverpod の ChangeNotifierProvider でラップします。
final myNotifierProvider = ChangeNotifierProvider<MyNotifier>((ref) => MyNotifier());
ルートに ProviderScope を配置します。このプロバイダーを使用している箇所(例:ConsumerWidget 内)で、context.watch を ref.watch に置き換えてください。すべての ChangeNotifier をただちに Notifier に変換する必要はありません。
リーフから始める
まず依存関係のないプロバイダー(「リーフ」)を移行し、次にそれらに依存するプロバイダーを移行します。依存先の移行が済むまでは、ProxyProvider の移行を避けてください。
一度に 1 つのプロバイダーを移行する
一度に 1 つのプロバイダーを移行してテストします。ChangeNotifier の移行を完了するには、(1)Notifier + NotifierProvider に変換し、(2)そのプロバイダーに対するすべての context.watch を ref.watch に置き換えます。
ProxyProvider → ref.watch
Riverpod では、別のプロバイダー内で ref.watch を使ってプロバイダーを結合します。
final labelProvider = Provider<String>((ref) {
final userIdNotifier = ref.watch(userIdNotifierProvider);
return 'The user ID is ${userIdNotifier.userId}';
});
ChangeNotifierProxyProvider のような状態を持つ結合オブジェクトには、プロバイダー内で ref.listen を使い、別のプロバイダーに反応して自分の notifier を更新します。
遅延初期化をしない場合
Riverpod のプロバイダーは遅延初期化されます。起動時にデータを読み込んでおくには、ルートでプロバイダーを監視します(例:アプリを子として返す、ProviderScope 配下の Consumer 内)。riverpod-eager-initialization を参照してください。
コード生成
コード生成では ChangeNotifierProvider は生成されません。移行中に ChangeNotifier を @riverpod で公開するには、小さな拡張(listenAndDisposeChangeNotifier)を使えます。Notifier に切り替えたら、この拡張を削除してください。コードスニペットについては公式クイックスタートを参照してください。
Provider と Riverpod の比較
プロバイダーの定義
- Provider: プロバイダーはウィジェットです(例:MultiProvider 内)。
- Riverpod: プロバイダーはトップレベルの final 変数です。定義にウィジェットツリーは必要ありません。アプリのルートに ProviderScope を追加します。
読み取り
- 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 と同じ考え方です。
コンシューマー
Riverpod には (context, ref, child) を受け取る Consumer があります。Consumer2、Consumer3 などは必要ありません。1 つのビルダー内で ref.watch を複数回呼び出すだけです。
スコープと family + autoDispose の比較
Provider では、状態を破棄したり、ページごとの状態を持たせたりするためにスコープを使用します。Riverpod では次のようになります。
- autoDispose はリスナーがいなくなると状態を破棄します(ref.onCancel / ref.onDispose)。
- .family はパラメーター化されたプロバイダーを作成します(パラメーターごとに 1 つの状態)。キャッシュが無制限に増えるのを防ぐため、autoDispose と併用してください。
つまり、スコープの代わりに、「X ごとの状態」には .family を、「未使用時に破棄」するには .autoDispose を使用してください。
詳しくは、riverpod-getting-started、riverpod-providers、riverpod-family、riverpod-auto-dispose をご覧ください。