【Flutter入門】StatefulWidgetで使われるStateのライフサイクルを徹底解説!

StatefulWidgetのStateのライフサイクルについて解説

本記事は、マルチプラットフォーム開発のSDK【Flutter】を用いたモバイルアプリ開発入門の為の記事です。

ダーフク

StatefulWidgetのStateは、初期化されてから破棄されるまで複数回のステップを踏みます。どのようなステップで行われているのか、深く理解することは開発を効率的に行うために不可欠です。本記事ではこのライフサイクルを解説していきます。

StatefulWidgetに関しては、以下の記事を参考にして頂ければと思います。

StatelessWidgetとStatefulWidgetの違いとは【Flutter入門】StatelessWidgetとStatefulWidgetの違い StatefulWidgetが2つにわかれている理由【Flutter入門】StatefulWidgetはなぜ2つに分かれているのかについて解説するよ。

 

ライフサイクルの全体像

Stateのライフサイクルは大まかに10ステップに分かれています。

  1. createState
  2. mounted is true
  3. initState
  4. didChangeDependencies
  5. build
  6. didUpdateWidget
  7. setState
  8. deactivate
  9. dispose
  10. mounted is false

➀createState(Stateを作成する)

まず、FlutterのSDKはStatefulWidgetのcreateStateを呼び出し、Stateを作成します。

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() = _MyHomePageState();
}

class _MyHomePageState extends State MyHomePage {
  .....
}

➁mounted is true(Stateをマウントする)

➀にて作成されたStateはBuildContextに紐づけられます。

BuildContextとは端的に言うと、各WidgetのWidget Tree上の位置(そのWidgetがどこにあるのか)を制御するためのクラスです。

このタイミングで、Stateオブジェクトはマウントされたと認識されます。

➂initState(Stateを初期化する)

クラスのコンストラクターが呼び出されたあと、すぐにこのinitStateが呼ばれます。

最初に一度だけ実行されます。

オーバーライドできますが、その際は必ず親クラスのinitStateも呼び出す必要があります。

// これを記述することでオーバーライドと認識される
@override
initState() {
  // 必ず、super.initState()を呼ぶこと
  super.initState();
  // Streamをlistenしてデータを連続したデータを受け取れるようにする
  cartItemStream.listen((data) {
    _updateWidget(data);
  });
}

④didChangeDependencies

initStateを呼び出した後にはdidChangeDependenciesが呼び出されます。

didChangeDependenciesは、InheritedWidgetBuildContext.inheritFromWidgetOfExactTypeが実行された場合に呼び出されます。

InheritedWidgetとは、Treeの下位のWidgetから直接参照できるWidgetのことです(詳細は別記事にて解説します)。

それぞれの開発のフェーズ(開発・検収・本番など)に応じて接続するURLを変えたい場合、InheritedWidgetを用いて上位のWidgetでデータを設定しておいて、それを下位のWidgetからアクセスする。こうすることで、毎回親Widgetから子Widgetへパラメーターを渡す手間を省くことができます。

アクセス方法は、ConfigInheritedWidget.of(context).hogeUrlのような形で情報を取得します。

ただその場合contextにアクセスしたいのですが、initState内ではまだcontextにアクセスすることができません。

そこで、didChangeDependencies内でこれを実行し、InheritedWidgetからデータを取得するようにします。

➅build

build関数はほぼ確実に使われます。描画したいWidgetを記述するのに用います。

@override
Widget build(BuildContext context) {
  return Widget();
}

➆didUpdateWidget(Widget oldWidget)

親Widgetが変更されて(親Widgetから渡されるパラメーターの値が代わり)、再ビルドされる必要がある場合にこのdidUpdateWidgetが呼ばれます。

FlutterではStateは使い回しますが、initStateで初期化をしたデータは再び初期化しなければならない場合があります。

もしStateのbuild関数がStreamや他のオブジェクトに依存していた場合、古いオブジェクトを購読解除し、didUpdateWidgetで作られた新しいオブジェクトを購読し直すようにすることができます。

これが呼ばれたのちにbuild関数も呼び出されるため、setStateをこの関数内に記述する必要はありません。

➆setState

State内の変数の値を変更したい場合に呼び出されます。これが呼ばれた後、build関数が再実行されます。

この関数は、同期的なコールバックを持ちます。これは、再描画が安価であるため、必要に応じて頻繁に呼び出すことができるためです。

void updateProfile(String name) {
 setState(() => this.name = name);
}

➇deactivate

これはほとんどの場合、使われません。Stateがツリーから削除される場合に呼び出されます。

➈dispose

Stateオブジェクトが削除されるとdisposeが呼び出されます。これは永続的なものです。

このメソッドでは、以下のようなものを取り消す場合に用いられます。

  • アニメーションの購読
  • Streamの購読
  • コントローラーの参照

➉mounted is false

disposeが呼び出されると、Stateオブジェクトは再マウントされません。

ここでsetState関数を呼び出した場合、エラーとなります。

まとめ

本記事では、Stateのライフサイクルについて解説しました。まとめると、以下のような手順になります。

あまり使わないため、意識しないステップもありますが、根本を理解しておくとエラーを見つける役に立つ場合もあります。

しっかりと理解をしておきましょう。

  1. createState
  2. mounted is true
  3. initState
  4. didChangeDependencies
  5. build
  6. didUpdateWidget
  7. setState
  8. deactivate
  9. dispose
  10. mounted is false

参考

参考 Stateful Widget LifecycleFlutter by example 参考 State classFlutter.io