リクエストに引数を渡す
前の記事では、シンプルなGET HTTP リクエストを行うための"provider"を定義する方法を見てきました。
しかし、HTTP リクエストは外部パラメータを必要とすることがよくあります。
例えば、以前はBored APIを使ってユーザーにランダムなアクティビティを提案しました。
しかし、ユーザーはアクティビティのタイプをフィルタリングしたいかもしれませんし、価格要件などもあるかもしれません。
これらのパラメータは事前にはわかりません。
そのため、これらのパラメータを UI から provider に渡す方法が必要です。
provider を引数を受け取るように更新する
以前、provider を次のように定義しました:
// "関数型" provider
Future<Activity> activity(Ref ref) async {
// TODO: アクティビティをフェッチするためにネットワークリクエストを実行する。
return fetchActivity();
}
// または "notifier"
class ActivityNotifier2 extends _$ActivityNotifier2 {
Future<Activity> build() async {
// TODO: アクティビティをフェッチするためにネットワークリクエストを実行する。
return fetchActivity();
}
}
provider にパラメータを渡すには、アノテーションが付いた関数自体にパラメータを追加するだけです。
例えば、定義したいアクティビティの種類に対応する String
引数を受け取るように provider を更新することができます:
Future<Activity> activity(
Ref ref,
// providerに引数を追加することができます。
// パラメータの型は何でも構いません。
String activityType,
) async {
// 引数 "activityType "を使ってURLを作ることができる。
// このポイントは "https://boredapi.com/api/activity?type=<activityType>"を指しています。
final response = await http.get(
Uri(
scheme: 'https',
host: 'boredapi.com',
path: '/api/activity',
// クエリパラメータを手動でエンコードする必要はなく、 "Uri" クラスが行ってくれます。
queryParameters: {'type': activityType},
),
);
final json = jsonDecode(response.body) as Map<String, dynamic>;
return Activity.fromJson(json);
}
class ActivityNotifier2 extends _$ActivityNotifier2 {
/// Notifier 引数はbuildメソッドで指定します。
/// 引数は好きなだけ増やすことができ、optional/名前付き引数にすることも可能です。
Future<Activity> build(String activityType) async {
// 引数は "this.<argumentName>" でも指定できます。
print(this.activityType);
// TODO: ネットワークリクエストを実行してアクティビティを取得します。
return fetchActivity();
}
}
provider に引数を渡すときは、provider に"autoDispose"を有効にすることを強くお勧めします。
これを怠るとメモリリークが発生する可能性があります。
詳細についてはauto_disposeを参照してください。
引数を渡すために UI を更新する
以前は、ウィジェットが次のように provider を消費(consumed)していました:
AsyncValue<Activity> activity = ref.watch(activityProvider);
しかし、provider が引数を受け取るため、それを消費(consume)するための構文が少し異なります。
provider は今や要求されたパラメータを使用して呼び出す必要がある関数です。
UI を更新して、ハードコードされたアクティビティタイプを渡すことができます:
AsyncValue<Activity> activity = ref.watch(
// providerは、アクティビティタイプを期待する関数となります。
// 今回は単純に、定数の文字列を渡しましょう。
activityProvider('recreational'),
);
provider に渡されるパラメータは、アノテーション付き関数のパラメータ(“ref” パラメータを除く)に対応します。
同じ provider に異なる引数で同時にリッスンすることは可能です。
例えば、UI は"recreational"と"cooking"の両方のアクティビティを表示することができます:
return Consumer(
builder: (context, ref, child) {
final recreational = ref.watch(activityProvider('recreational'));
final cooking = ref.watch(activityProvider('cooking'));
// 両方のアクティビティを表示できます。
// 両方のリクエストは並行して行われ、正しくキャッシュされる。
return Column(
children: [
Text(recreational.valueOrNull?.activity ?? ''),
Text(cooking.valueOrNull?.activity ?? ''),
],
);
},
);
キャッシングの考慮事項とパラメータの制限
provider にパラメータを渡す場合、計算は引き続きキャッシュされます。
違いは、計算がパラメータごとにキャッシュされることです。
これは、同じ provider を同じパラメータで消費(consume)する 2 つのウィジェットがある場合、ネットワークリクエストは 1 つだけ行われることを意味します。
しかし、異なるパラメータで同じ provider を消費する 2 つのウィジェットがある場合、2 つのネットワークリクエストが行われます。
これを実現するために、Riverpod はパラメータの == 演算子に依存しています。
したがって、provider に渡されるパラメータが一貫した等価性を持つことが重要です。
一般的なミスは、provider のパラメータとして ==
をオーバーライドしない新しいオブジェクトを直接インスタンス化することです。
例えば、次のように List を渡したくなるかもしれません:
// 代わりに文字列リストを許可するようにactivityProviderを更新することができます。
// その後、監視呼び出しから直接そのリストを作成することができます。
ref.watch(activityProvider(['recreational', 'cooking']));
このコードの問題は、['recreational', 'cooking'] == ['recreational', 'cooking'] が false であることです。
そのため、Riverpod は 2 つのパラメータが異なると見なし、新しいネットワークリクエストを試みます。
これは、無限ループのネットワークリクエストを引き起こし、ユーザーに進捗インジケーターが永久に表示される結果となります。
これを修正するには、const リスト (const ['recreational', 'cooking']) を使用するか、
== をオーバーライドするカスタムリスト実装を使用できます。
このミスを見つけやすくするために、 riverpod_lintを使用し、
provider_parameters lint ルールを有効にすることをお勧めします。
そうすると、前述のスニペットには警告が表示されます。
インストール手順については riverpod_lint/custom_lint の有効化 を参照してください。