Skip to main content

Combining providers

まずは Providers を読んでください。
このガイドでは、プロバイダを組み合わせることについてのすべてを見ていきます。

プロバイダの組み合わせ#

既にシンプルなプロバイダの作り方を紹介しました。 しかし、実際にはプロバイダが他のプロバイダの状態を読み取りたいという状況は多々あります。

そのためには、プロバイダのコールバックに渡された ref オブジェクトを使用し、その watch メソッドを使用します。

一例として、次のようなプロバイダーを考えてみましょう。

final cityProvider = Provider((ref) => 'London');

次に、cityProviderを使用する別のプロバイダを作成します。

final weatherProvider = FutureProvider((ref) async {
// 他のプロバイダをリッスンするために `ref.watch` を使い、
// 使用したいプロバイダを渡します。ここでは cityProvider です。
final city = ref.watch(cityProvider);
// そして、その結果を使って、`cityProvider`の値に基づいて何かをすることができます。
return fetchWeather(city: city);
});

これで終わりです。別のプロバイダに依存するプロバイダを作成しました。

FAQ#

時間の経過とともに Listen している値が変化するとどうなりますか?#

Listen しているプロバイダによっては、得られる値が時間の経過とともに変化することがあります。 例えば、StateNotifierProvider をリッスンしている場合や、 リッスンしているプロバイダが ProviderContainer.refresh / context.refresh で強制的にリフレッシュされた場合などです。

watchを使用すると、Riverpodは値が変更されたことを検知し、必要に応じて 自動的に プロバイダを再実行します。

これは、計算された状態では有効です。 例えば、todo-listを公開するStateNotifierProviderを考えてみましょう。

class TodoList extends StateNotifier<List<Todo>> {
TodoList(): super(const []);
}
final todoListProvider = StateNotifierProvider((ref) => TodoList());

一般的なユースケースとしては、TODOのリストをUIでフィルタリングして、完了したTODO/未完了のTODOだけを表示するというものがあります。

このようなシナリオを簡単に実現するには、次のような方法があります。

  • 現在選択されているフィルター・メソッドを公開するStateProviderを作成します。

    enum Filter {
    none,
    completed,
    uncompleted,
    }
    final filterProvider = StateProvider((ref) => Filter.none);
  • フィルタリングされたTodo-Listを公開するために、フィルタリングメソッドとTodo-Listを組み合わせた別のプロバイダを作ります。

    final filteredTodoListProvider = Provider<List<Todo>>((ref) {
    final filter = ref.watch(filterProvider);
    final todos = ref.watch(todoListProvider);
    switch (filter.state) {
    case Filter.none:
    return todos;
    case Filter.completed:
    return todos.where((todo) => todo.completed).toList();
    case Filter.uncompleted:
    return todos.where((todo) => !todo.completed).toList();
    }
    });

そして、UIはfilteredTodoListProviderを Listen して、フィルタリングされたTodo-Listを聞くことができます。
このようなアプローチをとることで、フィルターやTodoリストのどちらかが変更されたときに、自動的にUIが更新されます。

このアプローチを実際に見るには、Todo List example のソースコードを見てください。

info

この動作は Provider に特有のものではなく、すべてのプロバイダで動作します。

例えば、watchFutureProvider を組み合わせて、検索機能や設定変更を実装することができます。

// 現在の検索フィルター
final searchProvider = StateProvider((ref) => '');
/// 時間経過とともに変化する設定
final configsProvider = StreamProvider<Configuration>(...);
final charactersProvider = FutureProvider<List<Character>>((ref) async {
final search = ref.watch(searchProvider);
final configs = await ref.watch(configsProvider.last);
final response = await dio.get('${configs.host}/characters?search=$search');
return response.data.map((json) => Character.fromJson(json)).toList();
});

このコードは、サービスからキャラクターのリストを取得し、構成が変更されたり、検索クエリが変更されたりするたびに、自動的にリストを再取得します。

Can I read a provider without listening to it?#

プロバイダを Listen せずに読み取ることはできますか?

時には、プロバイダのコンテンツを読み取りたいが、取得した値が変更されたときに、公開された値を再作成することなく、読み取りたいことがあります。

例えば、他のプロバイダから認証用のユーザートークンを読み込むRepositoryのようなものです。
watch を使って、ユーザートークンが変更されるたびに新しいRepositoryを作成することもできますが、 そのようなことをしてもほとんど意味がありません。

このような状況では、read を使用することができます。これは watch と似ていますが、 取得した値が変更されたときにプロバイダがその値の公開を再作成することはありません。

その場合、作成したオブジェクトに ref.read を渡すのが一般的な方法です。 そして、作成されたオブジェクトは、いつでも好きな時にプロバイダを読み取ることができるようになります。

final userTokenProvider = StateProvider<String>((ref) => null);
final repositoryProvider = Provider((ref) => Repository(ref.read));
class Repository {
Repository(this.read);
/// The `ref.read` function
final Reader read;
Future<Catalog> fetchCatalog() async {
String token = read(userTokenProvider).state;
final response = await dio.get('/path', queryParameters: {
'token': token,
});
return Catalog.fromJson(response.data);
}
}
note

また、ref.readではなく、refをオブジェクトに渡すこともできます。

final repositoryProvider = Provider((ref) => Repository(ref));
class Repository {
Repository(this.ref);
final ProviderRefBase ref;
}

ref.readを渡すことで得られる違いは、コードの冗長性が少し減ることと、オブジェクトが決してref.watchを使わないようにすることだけです。

プロバイダの中で read呼ばないでください
final myProvider = Provider((ref) {
// ここで `read` を呼び出すのはよくありません。
final value = ref.read(anotherProvider);
});

オブジェクトの不要な再構築を避けるために read を使用する場合は、My provider updates too often, what can I do? を参照してください。

コンストラクタのパラメータとして read を受け取ったオブジェクトをテストするには?#

Can I read a provider without listening to it? で紹介したパターンを使っている場合は、 どうやってオブジェクトのテストを書けばいいのか悩んでしまうかもしれません。

このような場合は、生のオブジェクトではなく、プロバイダを直接テストすることを検討してください。 そのためには、[ProviderContainer]クラスを使用します。

final repositoryProvider = Provider((ref) => Repository(ref.read));
test('fetches catalog', () async {
final container = ProviderContainer();
Repository repository = container.read(repositoryProvider);
await expectLater(
repository.fetchCatalog(),
completion(Catalog()),
);
});

My provider updates too often, what can I do?#

プロバイダの更新頻度が高いのですが、どうしたらいいですか?

オブジェクトが頻繁に再作成される場合は、プロバイダが気づいていないオブジェクトを Listen している可能性があります。

例えば、Configurationオブジェクトを Listen しているが、hostプロパティしか使っていない、というような場合です。
Configuration オブジェクト全体を Listen することで、host 以外のプロパティが変更されても、プロバイダが再評価されることになり、望ましくない可能性があります。

この問題を解決するには、Configurationの内で必要なもの(つまりhostだけ を公開する別のプロバイダを作ることです。

オブジェクト全体を Listen するのを避けてください。

final configsProvider = StreamProvider<Configuration>(...);
final productsProvider = FutureProvider<List<Product>>((ref) async {
// 設定に変更があった場合は、productsProviderが製品を再取得します。
final configs = await ref.watch(configsProvider.last);
return dio.get('${configs.host}/products');
});

必要なものだけを Listen することを勧めます。

final configsProvider = StreamProvider<Configuration>(...);
/// 現在のホストのみを公開するプロバイダ
final _hostProvider = FutureProvider<String>((ref) async {
final config = await ref.watch(configsProvider.last);
return config.host;
});
final productsProvider = FutureProvider<List<Product>>((ref) async {
// ホストのみを Listen します。設定の他の部分が変更されても、
// プロバイダが無意味に再評価されることはありません。
final host = await ref.watch(_hostProvider.future);
return dio.get('$host/products');
});