Flutterの記事を整理し本にしました
- 本稿の記事を含む様々な記事を体系的に整理し本にまとめました
- 今後はこちらを最新化するため、最新情報はこちらをご確認ください
- 10万文字を超える超大作になっています(笑)
はじめに
Flutterの状態遷移を整理しました。
initState
とかbuild
とか漠然とイメージしていたのですが、しっかり理解していなかったことが理解できましたw
まとめ
StatefulWidgeのライフサイクルとは
StatefulWidgetにはライフサイクルがあり、生成されてから破棄されるまでが細かく管理され、その状態に応じてメソッドが呼ばれます。
これらの状態を理解しておくことで、より高度な制御が可能です。
普段何気なく使っているbuild
やinitState
はまさにこのライフサイクルに関連するメソッドです。
build
やinitState
は画面が表示されるときに呼ばれますが、このメソッドの中で変数の初期化を行う場合、実は呼ばれるタイミングや何回呼ばれるかなどが異なっています。
全体像の理解を優先するために、厳密には若干ニュアンスが異なるという部分もあります。
より正確な情報は公式ドキュメント等をご確認ください
ライフサイクル超概要図
まずは、超概要図で基本の流れを見ていきます。
ある意味当たり前の図になっています。
ある画面(StatefulWidget)が呼ばれると、画面を作るために、メモリ上に必要なWidgetや変数が集められます。
その後、Widgetの親子関係に従って、画面が描画(レンダリング)されていきます。
StatefulWidgetは状態によって画面が変化するため、ユーザ操作やイベントを契機に画面の再描画が行われます。
そして、使い終わったStatefulWidgetは、メモリを開放するために破棄されます。
ライフサイクル概要図
つづいて、より細かくライフサイクルを見ていきます。
createState
,initState
,build
,setState
,dispose
あたりは、使ったことがある方が多いかと思います。
実際に呼び出されるメソッドを記載しましたが、全体の大枠は変わっていません。
メソッドの意味
画面構築
-
createState()
- statefulWidgetを構築するとすぐに呼ばれる
- Widgetツリーに状態を作るために呼ばれる
-
initState()
- Widgetツリーの初期化を行う
- 1度だけ呼ばれる
-
didChangeDependencies()
- stateオブジェクトの依存関係が変更されたときに呼び出される
- initStateの後に呼ばれるが、それ以外にも呼ばれることはある
再描画
- build()
- Widgetで作られるUIを構築する
- 様々な場所から繰り返し呼び出される
- 変更がある部分ツリーを検知して置き換える
- didUpdateWidget(Widget oldWidget)
- ウィジェットの構成が変更されるたびに呼び出される
- 親Widgetが変更され、再描画する必要があるときに呼び出される
- oldWidgetパラメータを取得して比較する
- setState
- 状態が変わったことをフレームワークに通知する
画面破棄
-
deactivate
- stateオブジェクトがツリーから削除するたびに呼び出される
-
dispose
- オブジェクトがツリーから完全に削除され、2度とビルドされなくなったら呼ばれる
上記のメソッドは必要に応じてオーバーライドして自分でカスタマイズすることができまずが、メソッドの中で、supser.xxx()で、親クラスのメソッドを呼び出すことを忘れないように注意してください
ソースコードで確認
上記の状態遷移が行われていることをソースコードで確認してみます。
HelloWorldのプログラムに、各メソッドを入れてprint
します。
nextpageメソッドでダミーの別ページに遷移させるのは、この画面を終わらせdeactivate
とdispose
を呼び出したいためです。
import 'package:flutter/material.dart';
import 'package:hello_world/dummy.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, this.title}) : super(key: key);
final String? title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
+ print("call setState");
_counter++;
});
+ nextpage();
}
+ // ダミーで画面遷移を行う
+ void nextpage() async {
+ {
+ // アイデア一覧へ遷移
+ await Navigator.of(context)
+ .pushReplacement(MaterialPageRoute(builder: (context) {
+ return DummyPage();
+ }));
+ }
+ }
+ @override
+ void initState() {
+ print("call initState");
+ super.initState();
+ }
+ @override
+ void didChangeDependencies() {
+ print("call didChangeDependencies");
+ super.didChangeDependencies();
+ }
@override
Widget build(BuildContext context) {
+ print("call build");
return Scaffold(
appBar: AppBar(
title: Text(widget.title!),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
key: Key('counter'),
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
key: Key('increment'),
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
+ @override
+ void didUpdateWidget(oldWidget) {
+ print("call didUpdateWidget");
+ super.didUpdateWidget(oldWidget);
+ }
+ @override
+ void deactivate() {
+ print("call deactivate");
+ super.deactivate();
+ }
+ @override
+ void dispose() {
+ print("call dispose");
+ super.dispose();
+ }
}
import 'package:flutter/material.dart';
class DummyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("dummy"),
),
body: Text("dummy"),
);
}
}
# 初期画面構築
I/flutter (12599): call initState
I/flutter (12599): call didChangeDependencies
I/flutter (12599): call build
# +をタップしsetStateで再描画
I/flutter (12599): call setState
I/flutter (12599): call build
# 画面遷移時に画面を破棄(タップから連続して実行される)
I/flutter (13375): call deactivate
I/flutter (13375): call dispose
# main画面でVSCodeでソースコードを変更保存しホットリロードする
I/flutter (13375): call didUpdateWidget
I/flutter (13375): call build