メインコンテンツに進む

リクエストの組み合わせ

これまで、リクエストが互いに独立している場合だけを見てきました。
しかし、一般的なユースケースとして、あるリクエストの結果に基づいて別のリクエストをトリガーする必要がある場合があります。

このため、リクエストに引数を渡すメカニズムを使用して、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 を破棄する可能性があります。