Riverpod のコード生成が書くもの
Riverpod の codegen は provider 宣言を関数やクラスへ置き換える。その生成前後、自動破棄の逆転、family の引数、Riverpod 3 で消えた Ref 型を追い、導入で得るものと払うものを整理する。
Riverpod の provider は、関数を直接コンストラクタへ渡して宣言できます。一方、検索したコードには
@riverpod や _$Counter が現れ、同じ provider なのに形がまるで違います。
連載「Riverpod と依存性注入」では、生成前の構造が見える非 codegen の形を使いました。しかし、
自動破棄の説明には @Riverpod(keepAlive: true) が登場し、その読み方はまだ残っています。
コード生成の手順そのものは、Freezed を扱った回ですでに見ました。今回はRiverpod が何を入力として 読み、どの provider を出力するのかに絞ります。最初に宣言の対応を見て、既定値と古い記事の 読み替えまで進みます。
§ 01SHAPE関数とクラスが provider 宣言になる
読み取り専用の非同期値を、非 codegen で宣言すると次の形です。
final currentUserProvider = FutureProvider<User>((ref) async => fetchCurrentUser(),isAutoDispose: true,);
codegen では、同じ処理を @riverpod を付けた関数として書きます。第一引数は共通の Ref です。
@riverpodFuture<User> currentUser(Ref ref) async {return fetchCurrentUser();}
生成器は関数名から currentUserProvider を作り、戻り型の Future<User> から非同期 provider を
選びます。利用側は生成方式を意識せず、ref.watch(currentUserProvider) から
AsyncValue<User> を受け取ります。
状態を更新するメソッドが必要なら、非 codegen では Notifier と provider を別々に宣言します。
final counterProvider =NotifierProvider<Counter, int>(Counter.new);class Counter extends Notifier<int> {@overrideint build() => 0;void increment() => state++;}
codegen では、注釈を付けたクラスが生成側の _$Counter を継承します。
@riverpodclass Counter extends _$Counter {@overrideint build() => 0;void increment() => state++;}
ここから counterProvider と、state や ref を使える基底型が生成されます。.g.dart には hash や
デバッグ用 metadata も入りますが、読む入口は次の対応だけで十分です。
| 注釈を付けるもの | 戻り値・状態 | 生成される入口 |
|---|---|---|
| 関数 | String | Provider<String> に対応する変数 |
async 関数 | Future<User> | FutureProvider<User> に対応する変数 |
_$Counter を継承するクラス | int | NotifierProvider<Counter, int> に対応する変数 |
関数は値を計算するだけの provider、クラスは公開メソッドで状態を更新できる provider、という役割の
違いは生成後も変わりません。短くなった宣言の裏で、これまで手で選んだ provider の型と変数が
組み立てられています1Riverpod の codegen は注釈付きの関数・クラスから provider を生成し、関数の引数を family として扱う。自動破棄が既定で、無効化には keepAlive: true を使う(Riverpod “About code generation”)。。
§ 02PACKAGES注釈・生成器・実行役を分ける
Flutter で codegen を使う構成は、通常の flutter_riverpod に三つの package を足します。
dependencies:flutter_riverpod:riverpod_annotation:dev_dependencies:build_runner:riverpod_generator:
役割は次のように分かれます。
riverpod_annotation… アプリのソースが import する@riverpod/@Riverpodの定義riverpod_generator… 注釈を読んで provider 宣言を出力する Riverpod 専用の builderbuild_runner… builder を見つけて実行し、生成処理全体を進める Dart 共通の build system
注釈はアプリのソースに残るので dependencies、生成器と実行役は開発時にだけ動くので
dev_dependencies です。build_runner 自身が Riverpod の構文を知るわけではありません。
Riverpod 固有の変換を知っているのは riverpod_generator です2riverpod_annotation は生成器向けの注釈を公開し、riverpod_generator は provider を生成する builder、build_runner は Dart の builder を実行する build system である(pub.dev の riverpod_annotation、pub.dev の riverpod_generator、pub.dev の build_runner)。。
§ 03DISPOSE自動破棄の既定が逆になる
ここが非 codegen との最大の違いです。非 codegen では、自動破棄を使う provider に
isAutoDispose: true を指定しました。codegen では自動破棄が既定です。
@riverpodSession session(Ref ref) => loadSession();
この sessionProvider は、購読者がいなくなると破棄の対象になります。宣言が短くなった結果、
自動破棄の指定が消えたのではなく、指定しない側が自動破棄へ変わっています。
状態を購読が切れたあとも保持したいなら、設定できる大文字の注釈を使います。
@Riverpod(keepAlive: true)AppConfig appConfig(Ref ref) => loadConfig();
小文字の @riverpod は既定設定で使う定数、大文字の @Riverpod(...) は引数を渡せる注釈です。
keepAlive: true は自動破棄を無効にします。非 codegen は opt-in、codegen は opt-outと対にして
覚えないと、書き換えたあとに状態の寿命が変わります1Riverpod の codegen は注釈付きの関数・クラスから provider を生成し、関数の引数を family として扱う。自動破棄が既定で、無効化には keepAlive: true を使う(Riverpod “About code generation”)。。
§ 04FAMILY引数がそのまま family になる
非 codegen の family は .family を provider 宣言に付け、受け取れる引数の形にも制約がありました。
codegen では、注釈を付けた関数の引数がそのまま family の引数になります。
@riverpodFuture<Product> product(Ref ref,String id, {String locale = 'ja',bool includeStock = false,}) async {return fetchProduct(id,locale: locale,includeStock: includeStock,);}
利用側も Dart の関数と同じ形で値を渡します。
final product = ref.watch(productProvider(productId,locale: 'ja',includeStock: true,),);
位置引数だけでなく、名前付き・省略可能・既定値付きの引数を組み合わせられます。引数の組み合わせごとに
別の provider 状態になる点と、安定した == / hashCode が必要な点は、非 codegen の family と
同じです。違うのは、family 専用の宣言へ引数を詰める代わりに、関数の signature へ直接書けることです。
§ 05RIVERPOD-3ExampleRef を Ref と読む
古い記事では、関数ベースの provider が次の形で書かれています。
@riverpodString greeting(GreetingRef ref) => 'Hello';
GreetingRef は、以前の生成器が provider ごとに作っていた Ref のサブクラスです。2.6 で非推奨に
なり、Riverpod 3 では削除されました。現在は provider の名前にかかわらず Ref を使います。
@riverpodString greeting(Ref ref) => 'Hello';
したがって、検索結果に ExampleRef や UserRef があれば、Riverpod 2 世代の codegen だと判断
できます。クラスベースでは build() の中に ref が用意されるので、引数へ Ref 型を書きません。
family の引数を生成された Ref オブジェクトから読む形も、Riverpod 3 では使えません。引数は
Ref の property ではなく、注釈を付けた関数や build() の引数として受け取ります。
FooRef を単に Ref へ置換するだけでなく、family の値をどこから取っているかも確認します
3Riverpod 3 では provider ごとの Ref サブクラスが削除され、family の引数も Ref オブジェクトから取得しなくなった(Riverpod 3 移行ガイド、riverpod_generator changelog)。。
§ 06CHOICE短さと生成工程を交換する
codegen を使うと、provider の種類を手で選ぶ箇所が減り、関数やクラスの戻り型から生成器が型を 導きます。family には普通の Dart の引数を使え、provider のソースを変更したときの stateful hot-reload や、デバッグ用 metadata も得られます。
一方で、ソースを変えたら生成工程を通す必要があり、生成ファイルが増え、解析と出力の時間がビルドへ 加わります。注釈のコードだけを読んでも、実際に公開される provider 変数や型が見えにくい場面も あります。
Riverpod の公式ガイドも、codegen は任意であり、すでに Freezed などの生成環境がある project なら
導入しやすい一方、Riverpod のためだけに生成工程を増やす価値は小さい場合がある、と判断軸を示して
います1Riverpod の codegen は注釈付きの関数・クラスから provider を生成し、関数の引数を family として扱う。自動破棄が既定で、無効化には keepAlive: true を使う(Riverpod “About code generation”)。。
連載「Riverpod と依存性注入」が非 codegen の宣言から始めたのも同じ理由です。
Provider / FutureProvider / NotifierProvider を一度手で選べば、生成器が何を肩代わりするかが
見えます。仕組みを理解したあとで、すでにある生成工程へ Riverpod を載せるなら、短い宣言と
family の柔軟さを素直に受け取れます。
判断するのは「注釈のほうが短いか」だけではありません。project が生成工程の費用をすでに払って いるか、その工程と引き換えに型選択と family の定型を減らしたいかです。
§ 07SUMMARY生成前後を往復して読む
- 関数ベースは
@riverpod+Ref。戻り型から provider の種類と provider 変数が生成される - クラスベースは
@riverpodを付けて_$Fooを継承し、build()と更新メソッドを書く riverpod_annotationは注釈、riverpod_generatorは Riverpod 専用 builder、build_runnerは builder の実行役- 自動破棄は 非 codegen では opt-in、codegen では既定。無効化は
@Riverpod(keepAlive: true) - codegen の family は関数や
build()の引数。名前付き・省略可能・既定値付きも使える - Riverpod 3 では provider ごとの
FooRefが消え、共通のRefへ統一された - codegen は宣言と family を短くする代わりに、生成ファイル・ビルド時間・生成工程を増やす
生成された fooProvider だけを見ても、元の宣言だけを見ても、Riverpod の codegen の全体像は
分かりません。関数やクラスが何を宣言し、生成器がどの provider へ翻訳したかを往復すると、古い記事も
現在のコードも同じ地図の上で読めます。
- [1] Riverpod の codegen は注釈付きの関数・クラスから provider を生成し、関数の引数を family として扱う。自動破棄が既定で、無効化には
keepAlive: trueを使う(Riverpod “About code generation”)。 ↩ - [2]
riverpod_annotationは生成器向けの注釈を公開し、riverpod_generatorは provider を生成する builder、build_runnerは Dart の builder を実行する build system である(pub.dev のriverpod_annotation、pub.dev のriverpod_generator、pub.dev のbuild_runner)。 ↩ - [3] Riverpod 3 では provider ごとの
Refサブクラスが削除され、family の引数もRefオブジェクトから取得しなくなった(Riverpod 3 移行ガイド、riverpod_generatorchangelog)。 ↩