メインコンテンツに進む

コード生成について

コード生成、ツールを使用してコードを自動生成するアイデアです。
Dart では、アプリケーションを"コンパイル"する際に追加のステップが必要です。
この問題は、近い将来、Dart チームがこの問題に対処しようとしているため、解決される可能性があります。

Riverpod では、コード生成は"provider"を定義するための構文を変更することを意味します。
以下が例です:

final fetchUserProvider = FutureProvider.autoDispose.family<User, int>((ref, userId) async {
final json = await http.get('api/user/$userId');
return User.fromJson(json);
});

コード生成を使用すると、次のように書くことができます:


Future<User> fetchUser(Ref ref, {required int userId}) async {
final json = await http.get('api/user/$userId');
return User.fromJson(json);
}

Riverpod を使う時、コード生成は完全にオプションです。
Riverpod を使用するために必須ではありません。
ただし、Riverpod はコード生成をサポートしており、使用を推奨しています。

Riverpod のコード生成のインストール方法と使用方法については、 開始方法ページを参照してください。
ドキュメントのサイドバーでコード生成を有効にすることを忘れないでください。

コード生成を使用すべきか?

Riverpod ではコード生成はオプションです。
その点を考慮すると、使用すべきかどうか疑問に思うかもしれません。

答えは:使用した方が良いです
コード生成を使うことは Riverpod を使用する上で推奨される方法です。
これは将来性があり、Riverpod を最大限に活用することができます。
多くのアプリケーションがすでにFreezedjson_serializableのパッケージでコード生成を使用しています。
その場合、プロジェクトはすでにコード生成の設定がされており、Riverpod を使用することは簡単です。

現在、コード生成はがオプションなのはbuild_runnerが多くの人に好まれないためです。
しかし、Static Metaprogrammingが Dart で利用可能になると、 build_runnerはもはや問題ではありません。
その時点で、コード生成を使用することが Riverpod で唯一の方法になるでしょう。

build_runnerを使用することが大きな問題であれば、その場合のみコード生成を使用しないことを検討してください。 ただし、その場合、一部の機能が使用できなくなり、将来的にコード生成に移行する必要があります。
その際、Riverpod はマイグレーションをスムーズに進めるためのツールを提供します。

コード生成を使用する利点は何?

"Riverpod でコード生成がオプションなら、なぜ使用するのか?"と思うかもしれません。

いつものように、パッケージを使う理由は:開発を楽にするためです。
これには以下が含まれますが、限定されるものではありません:

  • より良い構文、より読みやすく柔軟性があり、学習曲線が低い。
    • provider のタイプを気にする必要はありません。
      ロジックを書き、Riverpod が最も適した provider を選択します。
    • 構文は"よくないグローバル変数"を定義しているようには見えません。代わりにカスタム関数/クラスを定義します。
    • provider にパラメータを渡すことが制限されなくなりました。.familyを使用して単一の位置パラメータを渡すことを制限される代わりに、任意のパラメータを渡すことができます。 これには名前付きパラメータ、オプション、さらにはデフォルト値も含まれます。
  • ステートフルなホットリロード が Riverpod で書かれたコードに適用されます。
  • 追加のメタデータを生成し、それをデバッガーが拾い上げることで、より良いデバッグが可能になります。
  • いくつかの Riverpod の機能はコード生成でのみ利用可能になります。

構文

provider の定義:

コード生成を使って provider を定義する時、以下のようなことに注意してください:

  • provider は注釈付きの関数または注釈付きのクラスとして定義できます。 どちらもほぼ同じですが、クラスベースの provider は公開メソッドを含む利点があり、外部のオブジェクトが provider の状態を変更することができます(副作用)。 関数ベースの provider は、build メソッドだけを持つクラスベースの provider を記述するためのシンタックスシュガーであり、UI によって変更することはできません。
  • 全ての Dart のasyncプリミティブ(Future、FutureOr、および Stream)がサポートされています。
  • 関数が asyncとしてマークされると、プロバイダーは自動的にエラー/読み込み状態を処理し、AsyncValue を公開します。
Functional
(publicメソッドを使用して副作用を実行できません。)
Class-Based
(publicメソッドを使用して副作用を実行できます。)
Sync

String example(Ref ref) {
return 'foo';
}

class Example extends _$Example {

String build() {
return 'foo';
}

// Add methods to mutate the state
}
Async - Future

Future<String> example(Ref ref) async {
return Future.value('foo');
}

class Example extends _$Example {

Future<String> build() async {
return Future.value('foo');
}

// Add methods to mutate the state
}
Async - Stream

Stream<String> example(Ref ref) async* {
yield 'foo';
}

class Example extends _$Example {

Stream<String> build() async* {
yield 'foo';
}

// Add methods to mutate the state
}

autoDispose の有効/無効化:

コード生成を使う時、provider はデフォルトで自動破棄されます。
これは、リスナーがアタッチされていないときに自動的に破棄されることを意味します(ref.watch/ref.listen)。 このデフォルト設定は、Riverpod の理念によりよく一致しています。
非コード生成バリアントでは、package:providerから移行するユーザーに対応するため、autoDispose はデフォルトで無効になっていました。

自動破棄を無効にしたい場合は、keepAlive: trueアノテーションを記載することで可能です。

// AutoDispose provider (keepAlive is false by default)

String example1(Ref ref) => 'foo';

// Non autoDispose provider
(keepAlive: true)
String example2(Ref ref) => 'foo';

provider にパラメータを渡す (family):

コード生成を使用する場合、provider にパラメータを渡すためには、family修飾子を使用する必要はありません。
代わりに、provider のメイン関数は、名前付きパラメータ、オプションのパラメータ、デフォルト値を含む任意の数のパラメータを受け取ることができます。
ただし、これらのパラメータは依然として一貫性のある==を持つ必要があることに注意してください。
つまり、値はキャッシュされるか、パラメータは==をオーバーライドする必要があります。

FunctionalClass-Based

String example(
Ref ref,
int param1, {
String param2 = 'foo',
}) {
return 'Hello $param1 & param2';
}

class Example extends _$Example {

String build(
int param1, {
String param2 = 'foo',
}) {
return 'Hello $param1 & param2';
}

// Add methods to mutate the state
}

非コード生成バリアントからの移行:

非コード生成バリアントを使用する場合、provider の型を直接指定する必要があります。
以下は、コード生成に移行するための対応オプションです:

Provider
Before
final exampleProvider = Provider.autoDispose<String>(
(ref) {
return 'foo';
},
);
After

String example(Ref ref) {
return 'foo';
}
NotifierProvider
Before
final exampleProvider = NotifierProvider.autoDispose<ExampleNotifier, String>(
ExampleNotifier.new,
);

class ExampleNotifier extends AutoDisposeNotifier<String> {

String build() {
return 'foo';
}

// Add methods to mutate the state
}
After

class Example extends _$Example {

String build() {
return 'foo';
}

// Add methods to mutate the state
}
FutureProvider
Before
final exampleProvider =
FutureProvider.autoDispose<String>((ref) async {
return Future.value('foo');
});
After

Future<String> example(Ref ref) async {
return Future.value('foo');
}
StreamProvider
Before
final exampleProvider =
StreamProvider.autoDispose<String>((ref) async* {
yield 'foo';
});
After

Stream<String> example(Ref ref) async* {
yield 'foo';
}
AsyncNotifierProvider
Before
final exampleProvider =
AsyncNotifierProvider.autoDispose<ExampleNotifier, String>(
ExampleNotifier.new,
);

class ExampleNotifier extends AutoDisposeAsyncNotifier<String> {

Future<String> build() async {
return Future.value('foo');
}

// Add methods to mutate the state
}
After

class Example extends _$Example {

Future<String> build() async {
return Future.value('foo');
}

// Add methods to mutate the state
}
StreamNotifierProvider
Before
final exampleProvider =
StreamNotifierProvider.autoDispose<ExampleNotifier, String>(() {
return ExampleNotifier();
});

class ExampleNotifier extends AutoDisposeStreamNotifier<String> {

Stream<String> build() async* {
yield 'foo';
}

// Add methods to mutate the state
}
After

class Example extends _$Example {

Stream<String> build() async* {
yield 'foo';
}

// Add methods to mutate the state
}