【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