🎁

【Flutter】Provider 入門

2022/04/07に公開

ProviderパッケージはInheritedWidget のラッパーライブラリです。下位ツリーのWidgetから上位ツリーのWidgetが管理する状態にアクセスする手段を提供します。Google I/O'19でも紹介されており、非常にメジャーなパッケージとなっています。
https://www.youtube.com/watch?v=d_m5csmrf7I&t=1175s

実装

カウンターアプリをProviderを用いて開発してみます。
[Increment Count A]ボタン、[Increment Count B]ボタン、[Increment Count C]ボタンをタップすると"Counter A"、"Counter C"の表示が更新されるようになっています。動作検証の為"Counter B"の表示は更新しないようにしています。

サンプルソースは以下に置いています。
https://github.com/NAOYA-MAEDA-DEV/flutter_provider_sample

サンプルソースの解説

Providerパッケージのインストール

providerパッケージをインストールするため、pubspec.yamlに以下のコードを記述します。

ChangeNotifierを継承した状態クラスを作る

  • ChangeNotifier
    ChangeNotifier はリスナーに変更通知を行う機能を提供しているクラスです。Counter クラスはChangeNotifier を継承することでnotifyListeners を使用することができるようになっています。
  • notifyListeners
    リスナーに変更通知を行います。

countAcountBcountC が下位ツリーのウィジェットからアクセスする状態変数となります。incrementCounterAincrementCounterBincrementCounterCcountAcountBcountCそれぞれをインクリメントするメソッドです。

状態クラスを上位ツリーに配置する

  • ChangeNotifierProvider
    上位ツリーにChangeNotifierProvider を配置します。create にはChangeNotifier を継承したオブジェクトを返す関数を指定します。child に下位ツリーとなるWidgetを配置していきます。

下位ツリーのWidgetから上位ツリーの状態クラスにアクセスする

context.watchcontext.readcontext.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]ボタンをタップした回数の表示が更新されます。

もう少し踏み込んで、TextWidgetATextWidgetBTextWidgetC がリビルドされるタイミングも確認します。[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