Skip to main content

Reading a provider

このガイドを読む前に、まずProvidersをお読みください。

このガイドでは、プロバイダを使う方法について説明します。

プロバイダのreadする方法は複数あり、基準によって少しずつ異なります。
長くなりましたが、ここでは、プロバイダをreadするときに何を使うかを決めるための判断グラフを紹介します。

Reading Provider の判断グラフ

次に、個々のケースを見て、その仕組みを紹介します。
このガイドでは、以下のプロバイダーを検討します。

final counterProvider = StateProvider((ref) => 0);

何をreadするかを決める#

listenしたいプロバイダによっては、listen可能な値が複数ある場合があります。

例として、次のようなStreamProviderを考えてみましょう。

final userProvider = StreamProvider<User>(...);

このuserProviderをreadするときには

  • userProvider自身をlistenすることで、現在の状態を同期的にreadする:

    Widget build(BuildContext context, WidgetRef ref) {
    AsyncValue<User> user = ref.watch(userProvider);
    return user.when(
    loading: () => const CircularProgressIndicator(),
    error: (error, stack) => const Text('Oops'),
    data: (user) => Text(user.name),
    );
    }
  • userProvider.streamをlistenして、関連する[Stream]を取得する:

    Widget build(BuildContext context, WidgetRef ref) {
    Stream<User> user = ref.watch(userProvider.stream);
    }
  • userProvider.lastをlistenして、emitされた最新の値で解決する[Future]を取得する:

    Widget build(BuildContext context, WidgetRef ref) {
    Future<User> user = ref.watch(userProvider.last);
    }

詳細は、APIリファレンスを参照して、各プロバイダのドキュメントを参照してください。

Widgetの中でプロバイダを使う#

このセクションでは、FlutterのWidgetがどのようにプロバイダと連携するかを見ていきます。

ConsumerWidget#

ConsumerWidget は、StatelessWidget に似たウィジェットのベースクラスですが、プロバイダをlistenすることができます。

class Home extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// counterProviderによって公開された値をlistenします。
int count = ref.watch(counterProvider).state;
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Text('$count'),
),
);
}
}

counterProviderの値がどのようにwatchという関数で取得されているかに注目してください。
この関数は、ConsumerWidgetがプロバイダをlistenし、値が変更されたときに再構築するためのものです。

caution

ConsumerWidgetの引数として渡されるwatchメソッドは、
onPressedElevatedButtonの内部のように、非同期的に呼び出されるべきではありません。

ユーザーイベントに応じてプロバイダをreadする必要がある場合は、context.read(myProvider)を参照してください。

Consumer#

ConsumerConsumerWidgetであり、データを使用する必要のあるWidgetのみを再構築することで、
アプリケーションのパフォーマンスを最適化するために使用することができます。

例えば、前に見たConsumerWidgetのコードスニペットを更新して、
カウンターが変更されたときにTextだけを再構築するようにします。

class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Consumer(
// counterProviderの更新時にTextのみを再構築する
builder: (context, ref, child) {
// counterProviderによって公開された値をlistenする
int count = ref.watch(counterProvider).state;
return Text('$count');
},
),
),
);
}
}

この例では、UIがcounterProviderをlistenし、カウンターが変更されるとTextをリビルドします(そのTextのみ)。

useProvider (hooks_riverpod のみ)#

flutter_hooks/hooks_riverpodを使用している場合、ConsumerWidgetの代わりに[ref.watch()]を使用することができます。

class Count extends HookConsumerWidget {
@override
Widget build(BuildContext context) {
int count = ref.watch(counterProvider).state;
return Text('$count');
}
}

これはflutter_hooksRiverpodの両方を併用したい場合に便利です。
この構文は、ConsumerWidgetではサポートされていない機能もサポートしています。[select] です。

class Example extends HookConsumerWidget {
@override
Widget build(BuildContext context) {
bool isAbove5 = ref.watch(counterProvider.select((s) => s.state > 5));
return Text('Is counter > 5 ? $isAbove5');
}
}

この構文を使うと、isAbove5変数が変化した場合に のみ 、Widgetが再構築されます。
つまり、カウンターが 1 から 2 に変更されても、 Widgetが再構築されることはありません

context.read(myProvider)#

Consumerと[ref.watch]では、プロバイダを listen する方法を見てきました。

しかし、状況によっては、そのオブジェクトをlistenする価値がないこともあります。
例えば、ElevatedButtononPressedのためだけにオブジェクトが必要な場合があります。

私たちは、[ref.watch]/Consumerを使用することができます。

Consumer(builder: (context, ref, _) {
StateController<int> counter = ref.watch(counterProvider);
return ElevatedButton(
onPressed: () => counter.state++,
child: Text('increment'),
)
});

しかし、それでは効率が悪いでしょう。 listenされたプロバイダによっては、カウンタが実際にはElevatedButtonの構築に使用されていなくても、
カウンタが変更されたときにElevatedButtonが再構築されることがあります。

そこで、context.read(myProvider)が解決策となります。

これを使うことで、以前のコードをリファクタリングします。

@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => context.read(counterProvider).state++,
child: Text('increment'),
);
}

こうすることで、ボタンをクリックしても、カウンターは増えていきます。
しかし、無駄なリビルドを避けるために、プロバイダをlistenしなくなっています。

context.readメソッドが見当たらないのですが、これはなぜでしょうか?

もし、context.readが表示されない場合は、正しいパッケージをインポートしていない可能性があります。 このメソッドを表示するには、package:flutter_riverpodまたはpackage:hooks_riverpodのいずれかをインポートする必要があります。

info

listenするプロバイダによては、この作業が必要ない場合もあります。 例えば、StateNotifierProviderは、StateNotifierの状態をlistenせずに取得する方法を内蔵しています。

class Counter extends StateNotifier<int> {
Counter(): super(0);
void increment() => state++;
}
final counterProvider = StateNotifierProvider((ref) => Counter());
// ...
@override
Widget build(BuildContext context, WidgetRef ref) {
// Counter.stateをlistenせずにCounterを取得します
// counterが変更されても、ボタンが再構築されることはありません。
final Counter counter = ref.watch(counterProvider);
return ElevatedButton(
onPressed: counter.increment,
child: Text('increment'),
);
}
caution

Widget の build メソッド内での context.read の呼び出しを避けてください。
リビルドを最適化したい場合は、代わりにProviderでlistenされた値を抽出します。

ProviderListener#

プロバイダの変更後にルートをプッシュしたり、ダイアログを表示したりするために、
Widgetツリーが必要になる場合があります。

このような動作は、ProviderListener Widgetを使用して実装されます。

Widget build(BuildContext context) {
return ProviderListener<StateController<int>>(
provider: counterProvider,
onChange: (context, counter) {
if (counter.state == 5) {
showDialog(...);
}
},
child: Whatever(),
);
}

これにより、カウンターが5に達したときにダイアログが表示されます。

プロバイダの中で別のプロバイダをreadする#

プロバイダーを作る際によくあるのが、他のオブジェクトからオブジェクトを作りたいというケースです。

例えば、UserRepositoryからUserControllerを作成したい場合、
両方のオブジェクトは別のプロバイダで公開されます。

このようなシナリオは、プロバイダがパラメータとして受け取る ref オブジェクトを使用することで可能になります。

final userRepositoryProvider = Provider((ref) => UserRepository());
final userControllerProvider = StateNotifierProvider((ref) {
return UserController(
// userRepositoryProviderをreadし、その結果からUserControllerを作成します。
repository: ref.watch(userRepositoryProvider),
);
});

Combining Providers(プロバイダーの組み合わせ)のガイドには、
以下のような情報が掲載されていますので、ぜひご覧ください。

  • 時間の経過とともに変化する値からオブジェクトを作成するとどうなるか?
  • いくつかのグッドプラクティス

Dartのみの場合のプロバイダ外のプロバイダのread#

場合によっては、Flutterに依存していないパッケージのプロバイダをreadしたいこともあるでしょう。
よくあるケースは、Widgetとは関係のないクラスをテストする場合です。

このような状況では、プロバイダを操作するための低レベルのユーティリティであるProviderContainerを使用することができます。

次のスニペットは、Flutterを使わずにテストでプロバイダをreadする方法を示しています。

test('counter starts at 0', () {
final container = ProviderContainer();
StateController<int> counter = container.read(counterProvider);
expect(counter.state, 0);
});
danger

ProviderContainerのインスタンスをテスト間で再利用しないでください。 これにより、テストケース間でプロバイダの状態が適切にリセットされるようになります。