[Flutter] InheritedWidgetの使い方を理解する
InheritedWidget
は下位ツリーのWidgetから上位ツリーのWidgetが管理する状態にアクセスする手段を提供するWidgetです。同様の手段を提供しているProviderパッケージはInheritedWidget
をラップしたパッケージになります。Provider
パッケージの普及によりInheritedWidget
を使用する機会はほとんど無くなってきていますが、InheritedWidget
の仕組みを理解することでProvider
パッケージへの理解を深めることができます。
InheritedWidgetの特徴
- 下位ツリーのウィジェットから直近の
InheritedWidget
にO(1)
[1]でアクセスすることができる。 -
InheritedWidget
が持つ状態が変更された時、その状態にアクセスしたWidgetに対して必要に応じて変更を通知することができる(リビルドさせることができる)。
直近のInheritedWidgetへのアクセスと変更通知
直近のInheritedWidgetへのアクセスする方法は2つあり、特徴に若干の違いがあります。
- dependOnInheritedWidgetOfExactType
- getElementForInheritedWidgetOfExactType
dependOnInheritedWidgetOfExactType
context.dependOnInheritedWidgetOfExactType<HogeWidget>()
-
dependOnInheritedWidgetOfExactType<HogeWidget>
を呼び出したcontext
から、最も近いHogeWidget(InheritedWidget
を継承したWidget)を返す。 -
dependOnInheritedWidgetOfExactType<HogeWidget>
を使用してHogeWidgetが持つ状態にアクセスしたWidgetに対して、その状態が変更される度に変更を通知することができる(リビルドさせることができる)。 -
didChangeDependencies
以降のタイミングでしか呼べない。
getElementForInheritedWidgetOfExactType
context.getElementForInheritedWidgetOfExactType<HogeWidget>()
-
getElementForInheritedWidgetOfExactType<HogeWidget>
を呼び出したcontext
から、最も近いHogeWidget(InheritedWidget
を継承したWidget)を返す。 -
getElementForInheritedWidgetOfExactType<HogeWidget>
を使用してHogeWidgetが持つ状態にアクセスしたWidgetに対して、その状態が変更されても変更が通知されない(リビルドされない)。 -
initState
タイミングでも呼べる。
実装
サンプルソースは以下に置いています。
フローティングボタンをタップした回数を表示するシンプルなアプリです。動作検証の為、画面上側のText
ウィジェットはタップした回数の表示を更新しますが、下側のText
ウィジェットはタップした回数の表示を更新しないようにしています。
class _InheritedCounter extends InheritedWidget {
const _InheritedCounter(
{Key? key,
required Widget child,
required this.count
}): super(key: key, child: child);
final int count;
bool updateShouldNotify(_InheritedCounter old) => true;
static _InheritedCounter? of(BuildContext context, {required bool listen}) {
return listen
? context.dependOnInheritedWidgetOfExactType<_InheritedCounter>()
: context.getElementForInheritedWidgetOfExactType<_InheritedCounter>()?.widget as _InheritedCounter;
}
}
class _CounterPage extends StatefulWidget {
const _CounterPage({Key? key}): super(key: key);
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<_CounterPage> {
var _count = 0;
Widget build(BuildContext context) {
print('Built CounterPageState');
return
_InheritedCounter(
count: _count,
child: Scaffold(
appBar: AppBar(title: const Text('Inherited Widget Sample')),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: _increment,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
WidgetA(),
WidgetB()
],
)),
);
}
void _increment() {
setState(() {
_count++;
});
}
}
class WidgetA extends StatelessWidget {
const WidgetA({Key? key}) : super(key: key);
Widget build(BuildContext context) {
print('Built WidgetA');
return Center(
child: Text(
'CounterA: ${_InheritedCounter.of(context, listen: true)?.count}',
style: const TextStyle(
fontSize: 20
)
)
);
}
}
class WidgetB extends StatelessWidget {
const WidgetB({Key? key}) : super(key: key);
Widget build(BuildContext context) {
print('Built WidgetB');
return Center(
child: Text(
'CounterA: ${_InheritedCounter.of(context, listen: false)?.count}',
style: const TextStyle(
fontSize: 20
)
)
);
}
}
InheritedWidgetを継承した状態クラスを作る
- 下位ツリーのWidgetがアクセスするフィールド(状態)を用意する
final int count;
実装例ではcountが下位ツリーのWidgetからアクセスするフィールドとなっています。
- ofメソッドで自身にアクセスする手段を提供する
static _InheritedCounter? of(BuildContext context, {required bool listen}) {
return listen
? context.dependOnInheritedWidgetOfExactType<_InheritedCounter>()
: context.getElementForInheritedWidgetOfExactType<_InheritedCounter>()?.widget as _InheritedCounter;
}
Flutterでの慣例として自身にアクセスするためのメソッドであるof
メソッドを定義します。前項のdependOnInheritedWidgetOfExactType
とgetElementForInheritedWidgetOfExactType
を使用して自身にアクセスする手段を提供します。listenがtrueの時は変更の通知が行われるdependOnInheritedWidgetOfExactType
を実行、listenがfalseの時は変更の通知が行われないgetElementForInheritedWidgetOfExactType
を実行しています。
- updateShouldNotifyで変更の通知条件を設定する
updateShouldNotify(_InheritedCounter old) => true;
bool
trueを返した時はフィールドの変更が下位ツリーのWidgetに通知され、falseを返した時はフィールドの変更が下位ツリーのWidgetに通知されなくなります。これによりフィールドの変更内容によって通知を制限することができます。
上位ツリーにInheritedWidget配置する
上位ツリーに前項で作成した_InheritedCounterを配置します。のcountプロパティに下位ツリーのWidgetからアクセスするプロパティをセットし、childに下位ツリーとなるWidgetを配置していきます。
_InheritedCounter(
count: _count,
child: ...
)
下位ツリーのWidgetから直近のInheritedWidgetにアクセスする
_InheritedCounterで定義したof
メソッドを使用して、直近のInheritedWidget
のフィールドにアクセスしています。
_InheritedCounter.of(context, listen: true)?.count
WidgetA内ではof
メソッドの引数listenをfalse、WidgetB内ではof
メソッドの引数listenをtrueとしています。フローティングボタンをタップしてcountをインクリメントした時、countの変更通知を受け取るWidgetAはリビルドされ、カウントの更新がText
ウィジェットに反映されます。しかしcountの変更通知を受け取らないWidgetBはリビルドされず、カウントの更新がText
ウィジェットに反映されないようになっています。
実行結果
-
ランダウ記号と呼ばれる処理にかかる計算量表を表す時に使用される。下位ツリーウィジェットからアクセス先の
InheritedWidget
の距離が大きくなってもアクセス時間は一定となる。 ↩︎
Author And Source
この問題について([Flutter] InheritedWidgetの使い方を理解する), 我々は、より多くの情報をここで見つけました https://zenn.dev/naoya_maeda/articles/63c707ad8f36a2著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol