[Flutter] Providerの使い方を理解する
ProviderパッケージはInheritedWidget
のラッパーライブラリです。下位ツリーのWidgetから上位ツリーのWidgetが管理する状態にアクセスする手段を提供します。Google I/O'19でも紹介されており、非常にメジャーなパッケージとなっています。
実装
サンプルソースは以下に置いています。
「Increment Count A/B/C」ボタンをタップすると"Counter A"、"Counter C"の表示が更新されるようになっています。動作検証の為"Counter B"の表示は更新しないようにしています。
サンプルソースの解説
Providerをインストールする
pubspec.yamlを編集してProviderをインストールします。dependencies
にprovider: ^6.0.2
を追記してPub Get
を実行してください。
dependencies:
flutter:
sdk: flutter
provider: ^6.0.2
ChangeNotifierを継承した状態クラスを作る
class Counter extends ChangeNotifier {
var countA = 0;
var countB = 0;
var countC = 0;
void incrementCounterA() {
countA++;
notifyListeners();
}
void incrementCounterB() {
countB++;
notifyListeners();
}
void incrementCounterC() {
countC++;
notifyListeners();
}
}
-
ChangeNotifier
ChangeNotifier
はリスナーに変更通知を行う機能を提供しているクラスです。CounterクラスはChangeNotifier
を継承することでnotifyListeners
を使用することができるようになっています。 -
notifyListeners
リスナーに変更通知を行います。
countA/B/Cが下位ツリーのウィジェットからアクセスする状態変数となります。incrementCounterA/B/CメソッドはcountA/B/Cそれぞれをインクリメントするメソッドです。
状態クラスを上位ツリーに配置する
ChangeNotifierProvider(
create: (context) => Counter(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
TextWidgetA(),
TextWidgetB(),
TextWidgetC(),
ButtonWidgetA(),
ButtonWidgetB(),
ButtonWidgetC()
],
),
),
-
ChangeNotifierProvider
上位ツリーにChangeNotifierProvider
を配置します。create
にはChangeNotifier
を継承したオブジェクトを返す関数を指定します。child
に下位ツリーとなるWidgetを配置していきます。
下位ツリーのWidgetから上位ツリーの状態クラスにアクセスする
context.watch
、context.read
、context.select
のいずれかを使用して上位ツリーの状態クラスにアクセスします。
- context.watch
class WidgetA extends StatelessWidget {
const WidgetA({Key? key}) : super(key: key);
Widget build(BuildContext context) {
print('Built WidgetA');
return Center(
child: Text(
'Counter A: ${context.watch<Counter>().countA}',
style: const TextStyle(
fontSize: 20
)
)
);
}
}
context.watch
は状態クラスの変化を監視します。状態クラスのnotifyListeners
が実行されると、context.watch
を使用しているウィジェットに対して変更が通知され、ウィジェットがリビルドされます。
- context.read
class TextWidgetB extends StatelessWidget {
const TextWidgetB({Key? key}) : super(key: key);
Widget build(BuildContext context) {
print('Built TextWidgetB');
return Center(
child: Text(
'Counter B: ${context.read<Counter>().countB}',
style: const TextStyle(
fontSize: 20
)
)
);
}
}
context.read
は状態クラスの変化を監視しません。状態クラスのnotifyListeners
が実行されても、context.read
を使用しているウィジェットに対して変更通知が行われず、ウィジェットがリビルドされません。
context.read<Counter>().incrementCounterA()
ButtonWidgetA/B/C内でcontext.read
を通してincrementCounterA/B/Cを呼び出しています。状態クラスのメソッドを呼び出す時はcontext.read
を使用します。
- context.select
class TextWidgetC extends StatelessWidget {
const TextWidgetC({Key? key}) : super(key: key);
Widget build(BuildContext context) {
print('Built TextWidgetC');
return Center(
child: Text(
'Counter C: ${context.select((Counter counter) => counter.countC)}',
style: const TextStyle(
fontSize: 20
)
)
);
}
}
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"と表示されました。
Author And Source
この問題について([Flutter] Providerの使い方を理解する), 我々は、より多くの情報をここで見つけました https://zenn.dev/naoya_maeda/articles/01e51841d6fb8b著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol