リクエストの組み合わせ
これまで、リクエストが互いに独立している場合だけを見てきました。
しかし、一般的なユースケースとして、あるリクエストの結果に基づいて別のリクエストをトリガーする必要がある場合があります。
このため、リクエストに引数を渡すメカニズムを使用して、provider の結果を別の provider へのパラメータとして渡すことができます。
しかしこのアプローチはいくつか欠点があります:
- 実装の詳細が漏れることになります。
UI が他の provider によって使用されるすべての provider を知っている必要があります。 - パラメータが変更されるたびに新しい state が作成されます。
パラメータを渡すことで、パラメータが変更したときに前の state を維持する方法がありません。 - リクエストの組み合わせが難しくなります。
- 開発ツールは provider 間の関係を知らないため、ツールの有用性が低くなります。
これを向上させるため、Riverpod はリクエストの組み合わせに異なるアプローチを提供します。
基本: “ref” の取得
リクエストを組み合わせる全ての方法は 1 つの共通点があります:
それらはすべてRef
オブジェクトに基づいています。
Ref
オブジェクトは、すべての provider がアクセスできるオブジェクトです。
これにより、さまざまなライフサイクルリスナーにアクセスできるだけでなく、provider を組み合わせるさまざまなメソッドも提供します。
Ref
を取得できる場所は、provider のタイプによって異なります。
機能的な provider では、Ref
は provider の関数のパラメータとして渡されます:
int example(Ref ref) {
// "Ref "は、他のproviderを読むために使うことができます。
final otherValue = ref.watch(otherProvider);
return 0;
}
クラスの種類では、Ref
は Notifier クラスのプロパティです:
class Example extends _$Example {
int build() {
// "Ref "は、他のproviderを読むために使うことができます。
final otherValue = ref.watch(otherProvider);
return 0;
}
}
ref を使用して provider を読む
ref.watch
メソッド
Ref
を取得したので、これを使用してリクエストを組み合わせることができます。
主な方法は ref.watch
を使用することです。
一般的に、他のオプション以上にref.watch
を使用することを推奨します,
これが一般的にメンテナンスを簡単にします。
ref.watch
は provider を受け取り、最新の state を返します。
そして、監視された provider が変更されるたびに、provider は無効になり、次のフレームまたは次の読み取りで再構築されます。
ref.watch
を使うことで、ロジックが"リアクティブ"かつ"宣言的"になります。
つまり、必要に応じてロジックが自動的に再計算されるようになります。
また、更新メカニズムが"on change"のような副作用に依存しないことを意味します。
これは StatelessWidgets が動作する方法と似ています。
例えば、ユーザーの位置情報を監視する provider を定義し、
その位置情報を使用してユーザーの近くのレストランのリストを取得することができます。
Stream<({double longitude, double latitude})> location(Ref ref) {
// TO-DO: 現在位置を取得するstreamを返す
return someStream;
}
Future<List<String>> restaurantsNearMe(Ref ref) async {
// 現在位置を取得するため"ref.watch"を使用する。
// providerの後に".future "を指定することで、コードは少なくとも1つの場所が利用可能になるまで待ちます。
final location = await ref.watch(locationProvider.future);
// 現在位置に基づいてネットワークリクエストを作成することができました。
// 例えばGoogle Map APIを使用することもできます。:
// https://developers.google.com/maps/documentation/places/web-service/search-nearby
final response = await http.get(
Uri.https('maps.googleapis.com', 'maps/api/place/nearbysearch/json', {
'location': '${location.latitude},${location.longitude}',
'radius': '1500',
'type': 'restaurant',
'key': '<your api key>',
}),
);
// JSONからレストラン名を取得する。
final json = jsonDecode(response.body) as Map;
final results = (json['results'] as List).cast<Map<Object?, Object?>>();
return results.map((e) => e['name']! as String).toList();
}
監視された provider が変更され、リクエストが再計算されると、新しいリクエストが完了されるまで以前の状態が保持されます。
同時に、リクエストが保留中の間、"isLoading"と"isReloading"フラグがセットされます。
これにより、UI は前の状態や読み込みインジケーター、またはその両方を表示することができます。
ref.watch(locationProvider)
ではなく、ref.watch(locationProvider.future)
を使用したことに注目してください。
これは、locationProvider が非同期であるためです。そのため、初期値が利用可能になるのを待ちます。
もし .future
を省略すると、locationProvider
の現在の状態のスナップショットである AsyncValue
を受け取ります。
しかし、まだ場所が利用できない場合は、何もできません。
"命令型"に実行されるコード内で ref.watch
を呼び出すことは悪いプラクティスとされています。
これは、provider のビルド段階で実行されない可能性があるコードを意味します。
これには、"listener"コールバックや Notifier のメソッドが含まれます:
int example(Ref ref) {
ref.watch(otherProvider); // Good!
ref.onDispose(() => ref.watch(otherProvider)); // Bad!
final someListenable = ValueNotifier(0);
someListenable.addListener(() {
ref.watch(otherProvider); // Bad!
});
return 0;
}
class MyNotifier extends _$MyNotifier {
int build() {
ref.watch(otherProvider); // Good!
ref.onDispose(() => ref.watch(otherProvider)); // Bad!
return 0;
}
void increment() {
ref.watch(otherProvider); // Bad!
}
}
ref.listen
/listenSelf
メソッド
ref.listen
メソッドは ref.watch
の代替手段です。
これは、従来の"listen"/"addListener"メソッドに似ています。
provider とコールバックを受け取り、provider の内容が変更されるたびにそのコールバックを呼び出します。
ref.listen
から ref.watch
にリファクタリングすることが一般的に推奨されます。
後者は命令形であるため、エラーが発生しやすいためです。
しかし、ref.listen
は大きなリファクタリングを行わず、迅速にロジックの追加することに役立つことがあります。
ref.watch
の例を ref.listen
を使用して書き換えることができます。
int example(Ref ref) {
ref.listen(otherProvider, (previous, next) {
print('Changed from: $previous, next: $next');
});
return 0;
}
provider のビルド段階で'ref.listen
を使用することは安全です。
provider が再計算されると、以前のリスナーは削除されます。
代わりに、ref.listen
の戻り値を使用して、望むときにリスナーを手動で削除することもできます。
ref.read
メソッド
最後のオプションは ref.read
です。
provider の最新の state を返す点でref.watch
と似ています。
しかし、ref.watch
とは異なり、provider をリッスンしません。
そのため、ref.read
は Notifiers のメソッド内、ref.watch
を使用できない場所でのみ使用する必要があります。
class MyNotifier extends _$MyNotifier {
int build() {
// 悪い例です。 ここで "read "を使ってはいけません。
ref.read(otherProvider);
return 0;
}
void increment() {
// ここで"read"を使用することは問題ありません。
ref.read(otherProvider);
}
}
provider を監視しないため、ref.read
を provider で使用する際には注意が必要です。
リスナーがいない場合、provider は state を破棄する可能性があります。