Flutter: カウンターアプリを InheritedWidget で実装してみる
June 1, 2024
自己理解ために InheritedWidget
でのデータ保持&アクセスをステップバイステップで実装していきます。
1. InheritedWidget
で保持するデータに子ウィジェットからアクセスする
#
count
のデータを保持する InheritedWidget
を定義しておき、ルートのウィジェット付近に InheritedWidget
を配置します。こうすることで、InheritedWidget
の配下に位置するウィジェットは .of(context)
で count
を取得できるようになります。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class InheritedCounterWidget extends InheritedWidget {
const InheritedCounterWidget({
super.key,
required this.count,
required super.child,
});
final int count;
@override
bool updateShouldNotify(InheritedCounterWidget oldWidget) {
return count != oldWidget.count;
}
static InheritedCounterWidget of(
BuildContext context, {
bool listen = false,
}) {
final result = listen
? context.dependOnInheritedWidgetOfExactType<InheritedCounterWidget>()
: context
.getElementForInheritedWidgetOfExactType<InheritedCounterWidget>()
?.widget as InheritedCounterWidget?;
assert(result != null, 'No InheritedCounterWidget found in context');
return result!;
}
}
class MyApp extends StatelessWidget {
const MyApp({
super.key,
});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: InheritedCounterWidget(
count: 0,
child: Column(
children: [
MyText(),
],
),
),
);
}
}
class MyText extends StatelessWidget {
const MyText({
super.key,
});
@override
Widget build(BuildContext context) {
final counter = InheritedCounterWidget.of(context);
return Text(counter.count.toString());
}
}
2. InheritedWidget
の count
をカウントアップできるようにする
#
先ほどのコードだと、count
の値は定数 0 を設定しており、カウントアップすることができません。
今度は MyApp
の state
として _count
を保持しておき、InheritedWidget
でそれを配下から参照できるようにします。
これによりボタンを押すと面上の数字がカウントアップされるようになります。
なおこのとき .of(context)
での参照の仕方を次のように変更する必要があります。
final counter = InheritedCounterWidget.of(context); // -> dependOnInheritedWidgetOfExactType
👇
final counter = InheritedCounterWidget.of(context, listen: true); // -> getElementForInheritedWidgetOfExactType
そうしないとカウントアップしてもそれを参照している MyText
が再ビルドされないため、画面描画が更新されません。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class InheritedCounterWidget extends InheritedWidget {
const InheritedCounterWidget({
super.key,
required this.count,
required super.child,
});
final int count;
@override
bool updateShouldNotify(InheritedCounterWidget oldWidget) {
return count != oldWidget.count;
}
static InheritedCounterWidget of(
BuildContext context, {
bool listen = false,
}) {
final result = listen
? context.dependOnInheritedWidgetOfExactType<InheritedCounterWidget>()
: context
.getElementForInheritedWidgetOfExactType<InheritedCounterWidget>()
?.widget as InheritedCounterWidget?;
assert(result != null, 'No InheritedCounterWidget found in context');
return result!;
}
}
class MyApp extends StatefulWidget {
const MyApp({
super.key,
});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _count = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: InheritedCounterWidget(
count: _count,
child: Column(
children: [
const MyText(),
MyButton(onPressed: () {
setState(() {
_count++;
});
}),
],
),
),
);
}
}
class MyText extends StatelessWidget {
const MyText({
super.key,
});
@override
Widget build(BuildContext context) {
final counter = InheritedCounterWidget.of(context, listen: true);
return Text(counter.count.toString());
}
}
class MyButton extends StatelessWidget {
const MyButton({
super.key,
required this.onPressed,
});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.add),
onPressed: onPressed,
);
}
}
3. MyApp
の _count
ステートを外部に移動する
#
いまは MyApp
で _count
ステートを保持していますが、今後 InheritedWidget
で配下からアクセスさせたいステートが増えるにつれて、MyApp
でのステートの宣言が膨れ上がっていくことが予想されます。
今後コード量が増えていったときにも管理しやすくなるように MyApp
で保持している InheritedWidget
用のステートを外部に移します。リファクタリングですね。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class InheritedCounterWidget extends InheritedWidget {
const InheritedCounterWidget({
super.key,
required this.data,
required super.child,
});
final CounterWidgetState data;
@override
bool updateShouldNotify(InheritedCounterWidget oldWidget) {
return true;
}
}
class CounterWidget extends StatefulWidget {
const CounterWidget({
super.key,
required this.child,
});
final Widget child;
@override
State<CounterWidget> createState() => CounterWidgetState();
static InheritedCounterWidget of(
BuildContext context, {
bool listen = false,
}) {
final result = listen
? context.dependOnInheritedWidgetOfExactType<InheritedCounterWidget>()
: context
.getElementForInheritedWidgetOfExactType<InheritedCounterWidget>()
?.widget as InheritedCounterWidget?;
assert(result != null, 'No InheritedCounterWidget found in context');
return result!;
}
}
class CounterWidgetState extends State<CounterWidget> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return InheritedCounterWidget(
data: this,
child: widget.child,
);
}
}
class MyApp extends StatelessWidget {
const MyApp({
super.key,
});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: CounterWidget(
child: Column(
children: [
MyText(),
MyButton(),
],
),
),
);
}
}
class MyText extends StatelessWidget {
const MyText({
super.key,
});
@override
Widget build(BuildContext context) {
final counter = CounterWidget.of(context, listen: true);
return Text(counter.data.count.toString());
}
}
class MyButton extends StatelessWidget {
const MyButton({
super.key,
});
@override
Widget build(BuildContext context) {
final counter = CounterWidget.of(context);
return IconButton(
icon: const Icon(Icons.add),
onPressed: () {
counter.data.increment();
},
);
}
}
4. もう少しだけリファクタリングする #
counter.data.count
や counter.data.increment()
を見たときに data
を記述させるのが無駄に感じられます。.of()
の戻り値を少しだけリファクタリングして、呼び出す側からより簡潔に使えるようにします。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class InheritedCounterWidget extends InheritedWidget {
const InheritedCounterWidget({
super.key,
required this.data,
required super.child,
});
final CounterWidgetState data;
@override
bool updateShouldNotify(InheritedCounterWidget oldWidget) {
return true;
}
}
class CounterWidget extends StatefulWidget {
const CounterWidget({
super.key,
required this.child,
});
final Widget child;
@override
State<CounterWidget> createState() => CounterWidgetState();
static CounterWidgetState of(
BuildContext context, {
bool listen = false,
}) {
final result = listen
? context.dependOnInheritedWidgetOfExactType<InheritedCounterWidget>()
: context
.getElementForInheritedWidgetOfExactType<InheritedCounterWidget>()
?.widget as InheritedCounterWidget?;
assert(result != null, 'No InheritedCounterWidget found in context');
return result!.data;
}
}
class CounterWidgetState extends State<CounterWidget> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return InheritedCounterWidget(
data: this,
child: widget.child,
);
}
}
class MyApp extends StatelessWidget {
const MyApp({
super.key,
});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: CounterWidget(
child: Column(
children: [
MyText(),
MyButton(),
],
),
),
);
}
}
class MyText extends StatelessWidget {
const MyText({
super.key,
});
@override
Widget build(BuildContext context) {
final counter = CounterWidget.of(context, listen: true);
return Text(counter.count.toString());
}
}
class MyButton extends StatelessWidget {
const MyButton({
super.key,
});
@override
Widget build(BuildContext context) {
final counter = CounterWidget.of(context);
return IconButton(
icon: const Icon(Icons.add),
onPressed: () {
counter.increment();
},
);
}
}
参考 #
Dart API Document
- InheritedWidget class - widgets library - Dart API
- dependOnInheritedWidgetOfExactType method - BuildContext class - widgets library - Dart API
- getElementForInheritedWidgetOfExactType method - BuildContext class - widgets library - Dart API
ブログ
- Flutter の BuildContext と InheritedWidget を理解する | itome
- 【Flutter】InheritedWidget とは何か - Zenn
- InheritedWidget を完全に理解する | by mono | Medium
- InheritedWidget の目的と使い方 - Qiita
- InheritedWidget の使い方を理解する - Zenn