Flutter in Focusを見る その2

こんにちは。Flutterでアプリ開発しているNaoyaです。前回に続き、Flutter in Focusを見ていこうと思います。各動画の内容を簡単にまとめていきます。

YouTubeのプレイリストはこちら

6 How Stateful Widgets Are Used Best

まとめ

  • Widgetの保持するデータが変化し、それによってUIが変わる場合、Stateful Widgetを使う
  • Stateful Widgetを使うときは2つのクラスを作成する。WidgetクラスとStateクラス。
  • 動画の例ではWidgetは2つの責任を持っている
    • 不変なnameフィールド持つこと
    • State オブジェクトを生成すること
  • Stateオブジェクトは可変なcountフィールドとchildを生成するためのbuildメソッドを持っている

WidgetとElementツリーについて

  • FlutterがWidgetにElementの生成を指示し、そこでStateful Elementが生成される
  • Stateful ElementがWidgetにStateオブジェクトを生成するようお願いする(ここでcreateStateメソッドが呼ばれる)。生成されたStateオブジェクトをElementは保持する。
  • Stateful Elementが、保持したStateオブジェクトのbuildメソッドを呼ぶ

Stateの管理について

  • Stateを変更したいときはsetStateメソッドを使う。setStateメソッド内でプロパティを変更すればその変更をUIへ反映させることができる。
  • 動画の例では、setStateと同時にcountがインクリメントされている
  • さらにその際、StateオブジェクトがそのElementに対し変更があったというをつける(印がついていると次のフレームでrebuildされる)
  • 次のフレームで印がついているElementがbuildメソッドを呼び、新しい子要素を描画する(古い要素から新しい要素へ置き換える)
  • このWidgetの置き換えの際、同じ種類のWidgetの場合はElement自体は変わらず、Widgetへの参照だけを更新する
  • 動画の例では、作成したStateful Widgetが小要素としてText Widgetを持っている。もしこのとき親であるStateful Widgetが別のWidgetに置き換えられた(保持するnameフィールドの値が変わった)としても、同じ種類のWidgetである場合は、Stateful ElementとStateオブジェクトは再生成されたりはせず、そのまま維持され、Elementが自分自身にをつけbuildメソッドを呼び出し、再描画される。Stateはこのように管理されているため、Stateを維持したままWidgetのプロパティを変更する、まさにホットリロードのような動きを実現できるのである。
  • StateオブジェクトがWidgetの変更を検知する方法も用意されており、それはdidUpdateWidgetメソッドである。使う場合はこれをoverrideすればよい。

Stateful Widgetの使い時と問題

  • 開発者がFlutterに習熟すればするほど、StatefulWidgetを使う場面は減っていく。なぜなら使わなくていいような仕組みが準備されているから。
  • 例えば StreamBuilder。状態の変化を自分で検知しsetStateせずとも、Streamを渡せば自動的に変更に応じてrebuildしてくれる。これはFlutter Frameworkの一部である。
  • また、非常にネストの深いStateful Widgetでデータを小要素へ伝搬していくのは非常に面倒であり、このためにInherited Widgetが存在する。詳細は次回の Flutter in Focusで♪

7 Inherited Widgets Explained

まとめ

  • アプリが大きくなるとWidgetが何層にもネストされることがある。Widgetツリーの一番深いWidgetから最上位Widgetのデータを取得しようとすると、そのデータを途中全てのコンストラクタとbuildメソッドに渡さなければならず、非常に面倒である。
  • この問題を解決するためにあるのがInheritedWidgetである。これをツリーの最上位として配置する。そうすれば全ての子WidgetからInheritedWidgetを参照し、データを取得することができる。(ソース
  • Inherited(継承)Widgetと言ってはいるが、クラスの継承とは別のものであり、あくまでWidgetツリー上の継承という意味である
  • InheritedWidgetを利用するためにはまずInheritedWidgetを継承(extends)したクラス(動画の例ではInheritedNoseクラス。話者の大きな鼻が父、祖父から継承していることを例にInheritedWidgetを説明しているため)を作成する。これが最終的には最上位に置かれるWidgetとなる。このクラスは必須パラメータとしてWidget型のchildを持つ。
  • あとは子Widgetで参照したいデータをInheritedNoseクラスのフィールドとして追加すれば、子Widgetのbuildメソッドからcontext.inheritFromWidgetOfExactTypeでデータを読み込める
  • inheritFromWidgetOfExactTypeが呼ばれると、Flutterはその引数に与えられた型のInheritedWidgetをWidgetツリーを上って探す

ofメソッドでシンプルにInheritedWidgetを呼び出す

  • このinheritFromWidgetOfExactTypeを呼ぶstaticなofメソッドを作成したInheritedWidgetに定義しておけば、子Widgetでの呼び出しが短く書ける
  • このof記法はFlutter Frameworkでもよく見られる。例えばTheme.of(context)でアプリのテーマを取得する場合だ。
  • Themeも実はInheritedWidgetの一種である(ソース)。ScaffoldやFocus Scopeもそうだし、他にもたくさん存在する。

Inherited Widgetのフィールドはfinalである

  • InheritedWidgetのフィールドはfinalであるということには注意が必要だ。この値を変更するにはWidget全体をリビルドしなければならない。
  • InheritedWidgetは多くの場合アプリのライフサイクル全体において変更されない
  • ただし、フィールドがfinalだといっても、内部的には変更可能になっている。例えば、単なる値ではなくて、ロジックを持つServiceオブジェクトをInheritedWidgetに持たせることができる。
  • そのServiceからメソッドを呼び出したり、ServiceにStreamを持たせることもできる