Riverpodは、Flutterアプリの状態をウィジェットツリーの外側で保持し、必要な画面だけを再描画させるためのライブラリです。2025年9月10日に3.0.0が安定版になり、2026年9月時点の最新は3.4.3。StateNotifierProviderやStateProviderが通常のimportから外れ、コード生成の書き方も変わったため、2.x時代の解説記事をそのまま写すとコンパイルが通りません。
この記事では、3系で実際に動く最小構成、@riverpodによるコード生成の現行書式、2.xから移行するときに壊れる箇所、そして自動リトライや永続化の実際の仕様を、パッケージの配布物と公式ドキュメントで確認した内容だけで組み立てます。
まとめ:Riverpod 3系を導入する前に押さえる前提
先に結論をまとめます。
| 論点 | 2026年9月時点の結論 |
|---|---|
| 入れるパッケージ | Flutterアプリは flutter_riverpod 3.4.3 の1本 |
| 必要なSDK | Dart 3.12以上=Flutter 3.44.0以降 |
| コード生成 | riverpod_annotation 4.0.7+riverpod_generator 4.0.9、引数は Ref ref |
| StateNotifierProvider | legacy.dart からのimportで継続利用は可能 |
| 永続化・Mutation | experimental配下。破壊的変更がマイナー版でも入る |
導入前に確認するのがSDK要件です。Flutter 3.41系のままflutter_riverpod: ^3.0.0のような幅のある指定でflutter pub getを叩くと、エラーを出さずに1世代前の3.3.2で解決され、新しいAPIだけが見つからない状態になります。以降の章で、この判定と回避方法から順に確認します。
Riverpodが引き受ける役割とProviderScopeの位置づけ
Riverpodは公式サイトで「リアクティブ・キャッシングとデータバインディング・フレームワーク」と名乗っています。単なる値の置き場ではなく、依存の注入、非同期結果のキャッシュ、再計算のトリガー管理をまとめて引き受ける設計です。
たとえばAPIから取得したユーザー情報をProviderに入れると、その値をref.watchしているウィジェットだけが再描画されます。同じコンテナ内では状態が共有されるため、画面をまたいでも取得済みの結果をそのまま読めます(再計算や再試行が走れば通信は発生します)。使われなくなったProviderの破棄は、リスナーがゼロになってから1フレーム待ち、その時点でも使われていなければ実行される仕組みです。この自動破棄はコード生成なら既定で有効で、手書きのProviderではisAutoDispose: trueを渡して有効にします。
実行時の前提は1つだけで、アプリ全体をProviderScopeで包むことです。包み忘れるとNo ProviderScope foundというStateErrorが投げられます。Providerの値を保持しているのはこのウィジェットが内部に持つProviderContainerで、テストではウィジェットを介さずProviderContainer.test()で直接組み立てられます。
なお、BlocやProviderパッケージとの比較検討はRiverpodと他の状態管理ライブラリの違いで扱っています。ここでは選定を終えた前提で、3系の実装に絞ります。
Riverpod 3系のパッケージ構成とSDK要件(2026年9月時点)
pub.devのAPIで確認した現行バージョンは次のとおりです。いずれも2026年9月4日(日本時間)に同時公開されています。
| パッケージ | 最新版 | 必要なDart SDK | 用途 |
|---|---|---|---|
| flutter_riverpod | 3.4.3 | ^3.12.0 | Flutterアプリ本体 |
| riverpod | 3.4.3 | ^3.12.0 | Dart単体・サーバー側 |
| hooks_riverpod | 3.4.3 | ^3.12.0 | flutter_hooks併用時 |
| riverpod_annotation | 4.0.7 | ^3.12.0 | @riverpod の注釈 |
| riverpod_generator | 4.0.9 | ^3.12.0 | build_runner用の生成器 |
| riverpod_lint | 3.1.9 | >=3.13.0-0 | analysis_server_plugin方式のlint |
| riverpod_sqflite | 0.4.7 | ^3.12.0 | 永続化(実験的) |
Flutterアプリならflutter_riverpodだけを入れます。riverpodは内部で依存されるので、pubspecに両方書く必要はありません。むしろflutter_riverpodしか書いていないプロジェクトでpackage:riverpod/...をimportすると、flutter_lints既定のdepend_on_referenced_packagesに引っかかります。
要注意なのがSDKの下限です。flutter_riverpodのenvironment.sdkは3.3.2では^3.7.0でしたが、3.4.1以降は^3.12.0に上がりました。Dart 3.12.0を同梱する最も古いFlutter安定版はFlutter 3.44.0(2026年5月18日リリース)なので、3.4系を使う条件は実質的にFlutter 3.44.0以降になります。
さらにriverpod_lint 3.1.9はDart 3.13.0以上を要求します。Dart 3.13.0の同梱はFlutter 3.47.0(2026年8月12日)からなので、lintまで最新に揃えるならFlutter側も3.47系が必要です。現行の安定版は3.47.5(2026年9月18日、Dart 3.13.4)です。
導入手順:pubspecからProviderScope、最初のProviderまで
依存関係はコード生成を使う前提で次のように書きます。生成器はdev_dependenciesに置きます。
environment:
sdk: ^3.12.0
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^3.4.3
riverpod_annotation: ^4.0.7
dev_dependencies:
build_runner: ^2.16.1
riverpod_generator: ^4.0.9
次に、runAppに渡すウィジェットをProviderScopeで包みます。この外側でProviderを参照すると例外になるため、先に配置しておきます。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: Home());
}
}
あとはProviderを定義してref.watchで読むだけです。読み出し側のrefはConsumerWidget.buildの第2引数から受け取ります。既存のStatefulWidgetを置き換える場合はConsumerStatefulWidgetとConsumerStateを使い、ConsumerStateの中ではrefがフィールドとして生えているためbuild以外のメソッドからも触れます。
ref.watchとref.readの使い分けは3系でも変わりません。build内で値を購読するときはref.watch、ボタン押下などのイベントハンドラ内で1回だけ読むときはref.readです。購読はbuildのたびに作り直される仕組みなので、コールバックからref.watchを呼んでも意図した再描画にはつながりません。ref.listenに至ってはbuild外での呼び出しがアサーションで弾かれ、ref.listen can only be used within the build method of a ConsumerWidget というメッセージが出ます。
静的解析を効かせるならriverpod_lintを入れます。3系のriverpod_lintはcustom_lintではなくanalysis_server_pluginを使う方式に変わり、analysis_options.yamlにplugins:として書いてdart analyzeで結果を見ます。ProviderScopeの付け忘れを指摘するmissing_provider_scopeや、関数プロバイダの第1引数がRefかを見るfunctional_refなど、この記事で触れる落とし穴の多くはここで機械的に拾えます。
@riverpodによるコード生成の現行書式とbuild_runner運用
3系のコード生成でいちばん変わったのが、関数プロバイダの引数型です。2.x時代はCounterRefのような生成された専用型を受け取っていましたが、riverpod_generator 2.6.0で非推奨になり、3系では専用型そのものが削除されました。現行の書式はRef refです。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'label.g.dart';
@riverpod
String label(Ref ref) => 'Hello world';
class Home extends ConsumerWidget {
const Home({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Text(ref.watch(labelProvider));
}
}
状態とロジックをまとめたい場合はクラスに@riverpodを付け、_$クラス名を継承してbuildで初期値を返します。初期化処理はbuildに集約し、refはthis.refとして使えます。
@riverpod
class CounterNotifier extends _$CounterNotifier {
@override
int build() => 0;
void increment() => state++;
}
ここで生成されるProvider名はcounterNotifierProviderではなくcounterProviderです。riverpod_generator 4.0.9の既定オプションprovider_name_strip_patternがNotifier$になっており、クラス名末尾のNotifierを取り除いてからProviderを付けるためです。生成名が想定と違うときは、build.yamlでこのパターンを上書きできます。
生成の実行はdart run build_runner watchです。既存の解説でよく見る-d(--delete-conflicting-outputs)は、build_runner 2.15.0で削除済みオプションの一覧に移りました。渡しても「These options have been removed and were ignored」という警告が出るだけで効きません。2.16.0からは生成物が食い違っていれば既定で作り直される動作に変わっており、手で編集した生成ファイルを残したい場合だけ--keep-modified-outputsを付けます。CIなど1回だけ動かす場面ではbuildサブコマンドを使います。
キャッシュの寿命は注釈で指定します。@riverpodのkeepAliveは既定でfalse、つまりリスナーがいない状態が1フレーム続くと破棄されます。アプリの生存期間中ずっと保持したい設定値やクライアントは@Riverpod(keepAlive: true)と書きます。手書きのProviderではProvider(isAutoDispose: true)と引数で指定します。2.x時代のProvider.autoDisposeというビルダー経由の書き方も3.4.3に残っているため、既存コードを一斉に書き換える必要はありません。
Riverpod 2.xから3系への移行で壊れる箇所
3系への移行で確認する代表的な変更を次に示します。コンパイルエラーになる変更に加え、更新通知や例外伝播など実行時の挙動が変わるものも含みます。
| 変更点 | 2.xの書き方 | 3系での対応 |
|---|---|---|
| レガシーProvider | 本体importで利用 | legacy.dart を追加import |
| Refのサブクラス | FutureProviderRef など | Ref に統一 |
| AsyncValue | valueOrNull | value(エラー時はnull) |
| 更新の通知 | Providerごとに == または identical | == で判定 |
| エラーの伝播 | 元の例外がそのまま | ProviderException で包む |
レガシーProviderのimportには落とし穴があります。StateNotifierProviderとStateProviderはriverpod側とflutter_riverpod側のどちらのlegacy.dartにも入っていますが、ChangeNotifierProviderはFlutterに依存するためflutter_riverpod/legacy.dartにしか存在しません。Flutterアプリでは次の1行で統一するのが安全です。
import 'package:flutter_riverpod/legacy.dart';
ただしlegacy.dartは移行のための待避先です。新規実装でStateNotifierを選ぶ理由はほとんど無く、NotifierとAsyncNotifierへ寄せた方が、後述する自動リトライやMutationの恩恵を受けられます。両者の具体的な書き分けはNotifierProviderとStateNotifierProviderの違いで解説しています。
更新判定が==に変わった点も見落としやすい変更です。ミュータブルなリストをstate.add(...)で書き換えて再代入すると、同一インスタンスのまま等価と判定され、UIが更新されません。3系では新しいリストを作って代入するか、Notifier側でupdateShouldNotifyを上書きします。
ProviderExceptionによるラップは、別のProviderをref.watchして例外が再送出されるときに発生します。自分でAsyncValue.guardを通した例外はそのままAsyncValue.errorに入るため、catch句で型判定している箇所は経路ごとに確認が必要です。
3系で追加された実務向けAPIの実際の挙動
ここからは3系の新機能について、公式ドキュメントの説明と、pub.devで配布されているパッケージの実装の両方を突き合わせて確認します。説明の言い回しだけでは読み取れない上限値や既定値が実装側に書かれているためです。参照した一次情報はRiverpod公式のWhat is new in Riverpod 3.0と、riverpodパッケージのCHANGELOGです。
自動リトライの上限10回と対象外の例外型
3系ではProviderのbuildが失敗すると自動で再実行されます。既定の挙動はProviderContainer.defaultRetryに書かれていて、遅延は200ミリ秒から始まる指数バックオフ、上限は6400ミリ秒、再試行回数は10回で打ち切りです。
重要なのは打ち切り条件がもう1つあることで、error is ProviderException || error is Errorに該当する例外は最初から再試行されません。ErrorはDartにおけるプログラムの誤り(型エラーやnullチェック失敗)を表すため、バグ由来の失敗を無限に叩き続けない設計になっています。逆に言えば、通信失敗をExceptionのサブクラスではなくErrorで投げているコードは、リトライの対象から外れます。
再試行の方針はスコープ単位でも差し替えられます。
runApp(
ProviderScope(
retry: (retryCount, error) {
if (retryCount >= 2) return null;
return const Duration(seconds: 1);
},
child: const MyApp(),
),
);
Ref.mountedによる非同期処理後の生存確認
非同期処理の待機中に画面が閉じられると、完了後のstate代入が破棄済みProviderへの操作になります。3系ではBuildContext.mountedと同じ発想のRef.mountedが追加され、await後に生存を確認できるようになりました。mounted以外のrefやnotifierのメソッドは破棄後に呼ぶとUnmountedRefExceptionを投げる仕様に変わったため、await直後のチェックを習慣にしておくと安全です。なお3.2.0では、リビルド後の古いrefでmountedがtrueを返す不具合が修正されています。実行環境のバージョンが3.2.0未満なら、この挙動は当てにできません。
実験的APIの制約:永続化の保持期間とMutationの同時実行
オフライン永続化はpackage:flutter_riverpod/experimental/persist.dartから提供され、公式ドキュメントも「This feature is experimental and not yet stable」と明記しています。有効化はNotifierの拡張メソッドをbuild内で呼ぶ形です。次は抜粋で、実際にはJsonSqFliteStorageを返すstorageProvider、fromJsonとtoJsonを持つTodoモデル、このNotifierを公開するProviderが別途必要になります。
import 'dart:convert';
import 'package:flutter_riverpod/experimental/persist.dart';
import 'package:riverpod_sqflite/riverpod_sqflite.dart';
class TodosNotifier extends AsyncNotifier<List<Todo>> {
@override
Future<List<Todo>> build() async {
await persist(
ref.watch(storageProvider.future),
key: 'todos',
encode: jsonEncode,
decode: (json) => (jsonDecode(json) as List)
.map((e) => Todo.fromJson(e as Map<String, Object?>))
.toList(),
).future;
return state.value ?? <Todo>[];
}
}
ここで見落とされがちなのが保持期間です。StorageOptionsのcacheTimeは既定で2日に設定されており、期限切れのデータはアプリ再起動時または期限後の読み出し時に削除されます。ユーザー設定を長期保存する用途では、既定の2日という保持期間が要件に合わない場合があります。保持期間を延ばすならStorageOptions(cacheTime: ...)を明示します。なお、状態がJSON化できるクラスで構成されているなら、package:riverpod_annotation/experimental/json_persist.dartの@JsonPersist()をNotifierに付けることで、keyとencodeとdecodeの指定を生成側に任せられます。
同じく実験的なMutationにも注意点があります。実装のコメントに「Currently, mutations do not restrict concurrent calls in any capacity.」とあるとおり、二重実行は抑止されません。送信ボタンの連打対策は、Mutationの状態を見てUI側で無効化する形で自前に用意する必要があります。
つまずきやすいエラーと切り分けの順番
No ProviderScopeエラーの原因とスコープ配置
Providerを読む位置がProviderScopeの外側にあるときの例外です。runAppの引数を包み忘れているケースのほか、ProviderScopeをMaterialAppより内側に置いていて、ルートのNavigatorへ積んだダイアログがスコープの外に出た場合、テストでpumpWidgetに素のウィジェットを渡している場合にも起きます。テストではウィジェットをProviderScopeで包むか、ProviderContainer.test()を使います。
build_runnerの生成失敗とpart宣言・依存制約の確認
まずpart 'ファイル名.g.dart';の行があるかを確認します。この宣言が無いと生成器は対象ファイルを無視します。次に生成されたProvider名です。クラス名にNotifierを付けている場合、既定の除去パターンによって名前から消えるため、counterNotifierProviderを参照しているとコンパイルが通りません。解析器のバージョン不一致はSDKと依存パッケージの制約を調整して解消し、その後にdart run build_runner buildで出力を作り直します。
新しいAPIが見つからない場合のSDK・解決バージョン確認
3系で追加されたクラスやメソッドが解決できないときは、flutter --versionでDartの版を見て、pubspec.lockに記録されているflutter_riverpodの版と突き合わせます。Dartが3.12未満で指定が^3.0.0のような幅のある書き方だと、pubは警告なく3.3.2で解決を終えます。対処はFlutter本体を3.44.0以降へ上げることで、pubspecの書き換えだけでは解決しません。
Riverpod 3.4系への更新と実験的APIの採用を見送る条件
すべてのプロジェクトで3系へ上げるべきとは考えていません。判断が分かれるのは、Flutterの版を固定しているチームです。
端末検証の都合やCI基盤の制約でFlutter 3.41系以前に固定しているなら、3.4系は入りません。選択肢は、3.3.2で止めてFlutterの更新計画に合わせるか、Flutterごと上げる段取りを先に組むかのどちらかです。決めずに幅の広い指定を置いたままにすると、解決される版が開発者の手元のFlutterによって変わります。pubspec.lockをリポジトリで共有し、同じ依存条件で検証できる状態にしてください。
永続化とMutationを前提にした設計も、現時点では勧めません。experimental配下のAPIについては、riverpodのCHANGELOGに「They may be modified in breaking ways without a major version.」と方針が書かれています。メジャー版を上げずに壊す前提のAPIということです。業務要件として永続化が必須なら、Riverpodの外側にリポジトリ層を置き、保存の実装を差し替えられる形にしておく方が安全です。アプリ全体の層構成はFlutterのアーキテクチャ設計の考え方が参考になります。
よくある質問
Riverpod 3.0はいつリリースされましたか?
安定版の3.0.0は2025年9月10日に公開されました。その後は3.1.0(2025年12月26日)、3.2.0(2026年1月17日)、3.3.2(2026年6月10日)、3.4系(2026年7月以降)と更新が続いており、2026年9月時点の最新は3.4.3です。マイナー版でもSDK要件が上がる場合があるため、更新時はCHANGELOGとpubspecのenvironmentを合わせて確認してください。
riverpodとflutter_riverpodはどちらを入れますか?
Flutterアプリならflutter_riverpodです。riverpodはFlutterに依存しないコア部分で、サーバーサイドDartやCLIなどウィジェットを使わない環境向けに分かれています。flutter_riverpodを入れれば依存として自動的に入るので、pubspecに両方書く必要はありません。flutter_hooksを併用しているプロジェクトでは、代わりにhooks_riverpodを入れます。
@riverpodとriverpod_annotationの関係は何ですか?
@riverpodという注釈を定義しているのがriverpod_annotationパッケージで、その注釈を読んで実際のProviderコードを書き出すのがriverpod_generatorです。前者はdependencies、後者はbuild_runnerと一緒にdev_dependenciesへ入れます。バージョンはannotationが4.0.7、generatorが4.0.9と番号がずれていますが、generator 4.0.9のpubspecがriverpod_annotation: 4.0.7をバージョン固定で依存しているため、両方に^付きで書いておけば整合します。
StateNotifierProviderはもう使えませんか?
使えます。3系では通常のimportから外れただけで、削除はされていません。import 'package:flutter_riverpod/legacy.dart';を追加すれば従来どおり動きます。ただし公式の方向性はNotifierとAsyncNotifierへの移行で、自動リトライやMutationなど3系の新機能はこちらを前提に設計されています。既存コードは動かしたまま、新規に書く部分からNotifierへ寄せるのが現実的です。
autoDisposeは3系でどう書きますか?
コード生成を使う場合、@riverpodは既定でautoDispose(keepAliveがfalse)です。保持したいときだけ@Riverpod(keepAlive: true)と書きます。手書きの場合はProvider(isAutoDispose: true)と引数で指定するのが現行の書き方で、2.x時代のProvider.autoDisposeというビルダー経由の書き方も3.4.3に残っています。AutoDisposeNotifierのような専用インターフェースはNotifierへ統合されたため、クラスを使い分ける必要はありません。