1 of 32

Everything is an “Element”

2021.11.29 (Mon)

FlutterKaigi

@chooyan-eng

2 of 32

自己紹介

中條 剛(ちゅうじょう つよし)

  • フリーランスエンジニア
  • Flutterでアプリ開発してます
  • 講師・メンターもしています
  • パッケージ開発もしています

内側から理解するFlutter

Zenn で販売中�

3 of 32

話すこと

Element とは

  1. Widgetとの比較
  2. 3つのElement

Elementの役割

  1. ツリーのビルド
  2. 描画の最適化
  3. ツリーの祖先、子孫へのアクセス

Flutter の「内部実装」の話

4 of 32

なぜ内部実装を学ぶのか

  • Widget やパッケージを効率的に使う
    • Widget やパッケージはとても多いため、「使い方」という切り口で覚えようとすると大変。「仕組み」で理解すると効率的。�
  • エラーの原因を理解する
    • ググって出てきた対策を入れてみるのではなく、エラーメッセージを読んで原因を理解できるようになれば効率的。�
  • 「あれ?これどうやれば実現できるんだろう?」を解決する
    • 仕組みを理解していれば、一番最適で最短な実装イメージがまず思い浮かぶようになる。

5 of 32

なぜ内部実装を学ぶのか

Flutter を学ぶ上で出てくる疑問も自分の頭で答えを出せる

  • 「StatefulWidget って provider があれば使わないの?」
  • 「riverpod と provider ってどっち使えばいいの?」
  • 「なんか build() が思ったより頻繁に呼ばれるんだけどこれ大丈夫?」
  • 「Widget の表示・非表示切り替えって Visibility と Offstage どっち使えばいいの?」
  • など

6 of 32

なぜ内部実装を学ぶのか

flutter.dev とフレームワークのコードを読んで

正確な理解を深めよう

7 of 32

デモ:Flutterフレームワークのコードにジャンプする

  1. Flutterプロジェクトを開く
  2. 調べたいWidgetを適当な場所に書く
  3. F12を押す
  4. 読む

これだけ

8 of 32

Element とは

  • Widgetとの比較
  • 3つのElement

9 of 32

Element とは

UIを構築するための「ツリー」を形成するオブジェクト

Widget から生成されるもの (”An instantiation of a Widget”)

Flutter フレームワークの内部実装の大部分を占める

10 of 32

Elementとは ~ Widgetとの比較 ~

Widget

Element

  • 「設定」を保持する
  • 「不変」なオブジェクト
  • 頻繁に破棄・再生成される
  • 実装はほぼ無い
  • データを保持するのが主
  • 描画に必要な一切のオブジェクトの参照を保持する。
  • 最大限、再利用される。
  • Flutter フレームワークの大半のロジックを担当する。

11 of 32

Elementとは ~ ツリー全体のイメージ ~

Widget

Element

RenderObject

StatelessWidget

RenderObjectWidget

StatefulWidget

State

12 of 32

Elementとは ~ 3つのElement ~

ComponentElement

InheritedElement

RenderObjectElement

ツリーを構築するための Element。

build() の結果生成される Widget から Element の親子関係を組み立てる

ツリーの子孫から O(1) でアクセスできる Element

データが変化したらアクセスのあった Element をリビルドする

具体的にレイアウトを計算し画面に描画する RenderObject を管理する Element

13 of 32

Elementの役割

  • ツリーのビルド
  • 描画の最適化
  • ツリーの祖先、子孫へのアクセス

14 of 32

Elementの役割 ~ ツリーのビルド ~

Q: なぜ StatefulWidget の build() は State に定義するのか

ビルド(リビルドも同様)の大まかな流れ

  1. Element performRebuild() が呼ばれる
    1. ComponentElement の場合はさらに build() が呼ばれる
  2. 生成された Widget から順番に Element が生成される(もしくは使いまわされる)
  3. 「リビルドの伝播」が終わる(後述)まで繰り返し

15 of 32

Elementの役割 ~ ツリーのビルド ~

Q: なぜ StatefulWidget の build() は State に定義するのか

1

2

createElement()

Element

build()

3

16 of 32

Elementの役割 ~ ツリーのビルド ~

Q: なぜ StatefulWidget の build() は State に定義するのか

POINT !

  • ComponentElement は build() を別のオブジェクトに移譲する
    • StatelessElement > StatelessWidget
    • StatefulElement > State

A: build() はあくまで Element の仕事で、具体的な処理をどのオブジェクトに移譲するかは Element の具体的な実装次第。と考えられる。

17 of 32

Elementの役割 ~ ツリーのビルド ~

Q: なぜ StatefulWidget の build() は State に定義するのか

StatelessElement

StatefulElement

18 of 32

Elementの役割 ~ 描画の最適化 ~

Q: リビルド範囲・回数は可能な限り絞るべき?

リビルドが伝播するかどうかを決める3つの基準

(「リビルドの伝播」... 子 Element の build() を呼ぶこと)

  • 新旧 Widget のインスタンスが同一
    • 伝播しない
  • 新旧 Widget の runtimeType と key が同一
    • Element を使いまわして伝播する
  • それ以外
    • Element を破棄・再生成して伝播する

19 of 32

Elementの役割 ~ 描画の最適化 ~

Q: リビルド範囲は可能な限り狭めるべき?

  • リビルドが発生した = Element の再生成 = 再描画
  • インスタンスが同一、もしくは runtimeType & key が同一であれば Element は使いまわされる
  • RenderObject もレイアウトの再計算・再描画は必要最低限に抑える最適化が施されている。

A: リビルドの範囲や回数よりもリビルドを無駄に伝播させない、Element を無駄に再生成させない工夫が大事。const を使おう!

20 of 32

Elementの役割 ~ ツリーの祖先、子孫へのアクセス ~

Q: .of(context) とは何か。ProviderNotFound エラーはなぜ発生するのか。

POINT !

  • context の実体は Element
  • .of(context) は context を起点に Element ツリーの祖先を遡って目当ての Element を見つける メソッド

StatelessElement

StatefulElement

21 of 32

Elementの役割 ~ ツリーの祖先、子孫へのアクセス ~

Q: .of(context) とは何か。ProviderNotFound エラーはなぜ発生するのか。

MyWidget

StatefulElement

見つかった!

Provider

Element

context

MaterialApp

StatefulElement

22 of 32

Elementの役割 ~ ツリーの祖先、子孫へのアクセス ~

Q: .of(context) とは何か。ProviderNotFound エラーはなぜ発生するのか。

MaterialApp

StatefulElement

MyWidget

StatefulElement

context

いくら遡っても Provider は無い!

Provider

Element

23 of 32

Elementの役割 ~ ツリーの祖先、子孫へのアクセス ~

Q: .of(context) とは何か。ProviderNotFound エラーはなぜ発生するのか。

A: .of(context) は Element である context を起点に Element ツリーを祖先方向に走査して目当ての Element を見つけるメソッド(のパターン)。具体的な実装は Widget によって異なる。走査した結果目当ての Element が見つからなかった場合にエラーが発生する。

24 of 32

Case: SharedAppDataを調査する

  • SharedAppData は StatefulWidget のサブクラス
    • State の build() で Widget を生成している

25 of 32

Case: SharedAppDataを調査する

  • _SharedAppModel は InheritedWidget のサブクラス
    • 子 Widget にデータを提供する。����
    • Map<Object, Object?> 型のなんでも入れられるデータらしい。

26 of 32

Case: SharedAppDataを調査する

  • setValue() getValue() はどんな実装になっている?

  • .of(context) と同じように Element の祖先から指定した型(_SharedAppModel)の Widget を探しているっぽい
  • 見つかったら先ほどの Map にデータを出し入れしているっぽい

27 of 32

Case: SharedAppDataを調査する

【結論】

  • InheritedElement の O(1) アクセスの仕組みを使って、Map<Object, Object?> の「何でも出し入れできる箱」に任意のデータを出し入れできる便利な Widget

28 of 32

Case: SharedAppDataを調査する

ということは?

  • 使う context は MaterialApp よりも子孫でなければならない
  • MaterialApp の子孫ということは複数ページで共通利用できそう
  • getValue() すると setValue() が呼ばれたときにリビルドされそう

詳しくはドキュメントへ

※ 2021.11.19 時点では Web ページ上にドキュメントが存在しないため、ソースコード中のコメントを読むとよさそう。

29 of 32

Case: SharedAppDataを調査する

※注意:

  • SharedAppData は「状態管理」のための Widget ではないそうです

30 of 32

まとめ

何はともあれ F12 を Hit しよう!

Element知れば Flutter が見えてくる

Flutter が見えてくると Flutter アプリ開発の 楽しさが倍増 する。はず。

31 of 32

あわせて読みたい

Inside Flutter

頑張ればきっと読める

内側から理解する Flutter | Zenn

日本語です。ぜひ。

32 of 32

ご清聴ありがとうございました

引き続きFlutterKaigiを楽しみましょう!