InheritedWidgetの特徴
- 下位ツリーのウィジェットから直近の
InheritedWidget
にO(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
タイミングでも呼べる。
実装
サンプルソースは以下に置いています。
https://github.com/NAOYA-MAEDA-DEV/flutter_inherited_sample
フローティングボタンをタップした回数を表示するシンプルなアプリです。動作検証の為、画面上側の
Text
ウィジェットはタップした回数の表示を更新しますが、下側の
Text
ウィジェットはタップした回数の表示を更新しないようにしています。
class _InheritedCounter extends InheritedWidget {
const _InheritedCounter(
{Key? key,
required Widget child,
required this.count
}): super(key: key, child: child);
final int count;
@override
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);
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<_CounterPage> {
var _count = 0;
@override
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);
@override
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);
@override
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がアクセスするフィールド(状態)を用意する
実装例ではcountが下位ツリーのWidgetからアクセスするフィールドとなっています。
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で変更の通知条件を設定する
@override
bool updateShouldNotify(_InheritedCounter old) => true;
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
ウィジェットに反映されないようになっています。
実行結果
