【Flutter】Provider 入門
ProviderパッケージはInheritedWidget のラッパーライブラリです。下位ツリーのWidgetから上位ツリーのWidgetが管理する状態にアクセスする手段を提供します。Google I/O'19でも紹介されており、非常にメジャーなパッケージとなっています。
実装
カウンターアプリをProviderを用いて開発してみます。
[Increment Count A]ボタン、[Increment Count B]ボタン、[Increment Count C]ボタンをタップすると"Counter A"、"Counter C"の表示が更新されるようになっています。動作検証の為"Counter B"の表示は更新しないようにしています。

サンプルソースは以下に置いています。
サンプルソースの解説
Providerパッケージのインストール
providerパッケージをインストールするため、pubspec.yamlに以下のコードを記述します。
ChangeNotifierを継承した状態クラスを作る
-
ChangeNotifier
ChangeNotifierはリスナーに変更通知を行う機能を提供しているクラスです。CounterクラスはChangeNotifierを継承することでnotifyListenersを使用することができるようになっています。 -
notifyListeners
リスナーに変更通知を行います。
countA 、countB 、countC が下位ツリーのウィジェットからアクセスする状態変数となります。incrementCounterA 、incrementCounterB 、incrementCounterC はcountA 、countB 、countCそれぞれをインクリメントするメソッドです。
状態クラスを上位ツリーに配置する
-
ChangeNotifierProvider
上位ツリーにChangeNotifierProviderを配置します。createにはChangeNotifierを継承したオブジェクトを返す関数を指定します。childに下位ツリーとなるWidgetを配置していきます。
下位ツリーのWidgetから上位ツリーの状態クラスにアクセスする
context.watch 、context.read 、context.select のいずれかを使用して上位ツリーの状態クラスにアクセスします。
- context.watch
context.watch は状態クラスの変化を監視します。状態クラスのnotifyListeners が実行されると、context.watch を使用しているウィジェットに対して変更が通知され、ウィジェットがリビルドされます。
- context.read
context.readは状態クラスの変化を監視しません。状態クラスのnotifyListenersが実行されても、context.readを使用しているウィジェットに対して変更通知が行われず、ウィジェットがリビルドされません。
ButtonWidgetA/B/C内でcontext.read を通してincrementCounterA/B/Cを呼び出しています。状態クラスのメソッドを呼び出す時はcontext.readを使用します。
- context.select
context.select は状態クラスが持つ特定のオブジェクトの変化を監視します。状態クラスのnotifyListeners が実行されると、context.select で指定したcountCの状態が変化した時のみウィジェットに対して変更が通知され、ウィジェットがリビルドされます。
実行結果
[Increment Count A]ボタンをタップしてみます。Counter オブジェクトのincrementA が実行され、countA がインクリメントされます。TextWidgetA ではcontext.watch を使用してCounter オブジェクトにアクセスしているため、Counter オブジェクトに変更が入るとTextWidgetA がリビルドされ、[Increment Count A]ボタンをタップした回数の表示が更新されます。

次に[Increment Count B]ボタンをタップしてみます。Counter オブジェクトのincrementB が実行され、countB がインクリメントされます。TextWidgetB ではcontext.read を使用してCounter オブジェクトにアクセスしているため、Counter オブジェクトに変更が入ってもTextWidgetB はリビルドされず、[Increment Count B]ボタンをタップした回数の表示が更新されません。

最後に[Increment Count C]ボタンをタップしてみます。Counter オブジェクトのincrementC が実行され、countC がインクリメントされます。TextWidgetC ではcontext.select を使用してCounter オブジェクトにアクセスしています。また、countC の変更を監視するようにしているため、countC に変更が入るとTextWidgetC がリビルドされ、[Increment Count C]ボタンをタップした回数の表示が更新されます。

もう少し踏み込んで、TextWidgetA 、TextWidgetB 、TextWidgetC がリビルドされるタイミングも確認します。[Increment Count A]ボタンをタップしてみます。TextWidgetA がリビルドされたことにより、コンソールに"flutter: Built TextWidgetA"が表示されました。

次に[Increment Count B]ボタンをタップしてみます。TextWidgetB がリビルドされず、コンソールに"flutter: Built TextWidgetA"のみが表示されました。

TextWidgetB ではcontext.read を使用してCounter オブジェクトにアクセスしたため、Counter オブジェクトに変更が入ってもTextWidgetB には変更が通知されずTextWidgetB はリビルドされません。しかしTextWidgetA ではcontext.watch を使用してCounter オブジェクトにアクセスしたため、counterB がインクリメントされたことでCounter オブジェクトに変更が入ったことが検知されます。その結果、[Increment Count B]ボタンをタップした時はTextWidgetA のみ変更が通知され、TextWidgetA はリビルドされています。
最後に[Increment Count C]ボタンをタップしてみます。countC がインクリメントされることによりCounter オブジェクトが変更され、TextWidgetA がリビルドされます。また、countC がインクリメントされたことにより、countC を監視していたTextWidgetC がリビルドされます。その結果、コンソールに"flutter: Built TextWidgetA"、"flutter: Built TextWidgetC"が表示されます。

Discussion