Open12

Flutter

expsh13expsh13

Widget

StatefulWidget のStateと違い、 InheritedWidget (というかWidget)は
一度インスタンスが生成された後、自身を変えることができない、
immutable(不変)の性質を持つからです。
https://blog.flutteruniv.com/flutter-inheritedwidget/

expsh13expsh13

描画

Widgetが描画される時、内部では、3種類の要素が活躍しています。
Widget , Element, RenderObjectです。

それぞれの役割は以下のようになっています。

Widget : そのWidgetの設定を管理するもの
Element : そのWidgetのツリー上での位置やライフサイクルを管理するもの
RenderObject : そのWidgetのサイズやレイアウト、描画を管理するもの

※build メソッドで使っているcontextとは、そのWidgetのElementです。

expsh13expsh13

Inherited Widget

dependOnInheritedWidgetOfExactTypeを使うと、引数に使用したcontextが、『Inherited Widgetを監視しているものリスト』に登録されます。

_inheritedWidgetsで保管していた祖先のInheritedElement、
並びにInheritedWidetを取得するメソッドとなっています。

このメソッドのすごいところは、祖先のInheritedWidgetを取得するのに、
がとても簡単なことです。

祖先のInheritedElementをすべての子のElementで保管していて、
その中から探すだけなので、どれだけ子が多かったとしても(ツリーが深かったとしても)
とても簡単に取得できるのです。
(計算量がO(1)で済みます。)
https://blog.flutteruniv.com/flutter-inheritedwidget/

expsh13expsh13

StatefulWidget

Flutterは特定のStatefulWidgetのStateでsetStateを実行するとリビルドがなされますが、必ずしも下位ツリー全体がリビルドされるわけではありません。同一インスタンスのWidgetの場合は不変であることが保証されるため、リビルドはされません。具体的には主に次のものが相当しますが、どれも同一インスタンスのWidgetとするための手段に過ぎず本質的にはどれも一緒です。

expsh13expsh13

ウィジェットの親が再構築されると、親は新しいインスタンスを作成しますが、フレームワークはツリーに既に存在するStateインスタンスを再利用し、再度createStateを呼び出しません。

When this widget's parent rebuilds, the parent creates a new instance of ShoppingList, but the framework reuses the _ShoppingListState instance that is already in the tree rather than calling createState again.

https://docs.flutter.dev/ui

expsh13expsh13

抽象クラス abstract

抽象クラスとは、具体的な振る舞いを持たないクラスのことです。メソッドやメンバの定義はできますが、インスタンス化できないため、そのままでは扱うことはできません。
https://qiita.com/hukusuke1007/items/8fd3411d2ef3c0d18429
https://docs.flutter.dev/cookbook/lists/mixed-list

expsh13expsh13

implementsの役割とメリット

  • インターフェースの強制
    implementsを使用することで、クラスが特定のメソッドを必ず実装することを強制できます。これにより、インターフェースの一貫性が保たれ、予期しない動作を防ぐことができます。
  • 複数のクラス
    implementsは複数のクラスを実装できます。
class Point implements Comparable, Location {...}

https://dart.dev/language/classes#implicit-interfaces

expsh13expsh13

Flutter ツリー

3種類のツリー

Widget

ステートレス、イミュータブル、軽量、設計図。作成と破棄のコスト小さい。

Element

ステートフル、ミュータブル、WidgetとRenderObjectの仲介役。極力再利用。

RenderObject

ステートフル、ミュータブル、レンダリングを担う。極力再利用。

Widgetは以下のことだけに徹した軽量オブジェクトです。

Widgetは以下のことだけに徹した軽量オブジェクト。

  • レンダリングに必要な情報の保持(例えばText Widgetでは文字列・スタイル情報など。座標などの具体的なレンダリング情報では無い。)
  • 自身のElementの生成
  • 子Widgetのbuild

Element

Elementは状態を持っていてこのあと生成済みのElementおよびそのツリー構造をできる限り再利用しようとします。StateObjectというものを作成し、状態を管理します。

まず、Elementには大別して2種類あります。

ComponentElement

他の Element を作るような Element です。
StatelessWidget、StatefullWidget、InheritedWidgetなどがcreateElementが呼ばれたときに返す。

RenderObjectElement

RenderObject への参照を持つ Element です。
RenderObjectElementが構築される際はcreateRenderObjectにより、RenderObjectツリーも構築。
setStateの際は再利用されたElementの場合は作成済みの保持しているRenderObjectを同じく再利用。
createElementが呼ばれた時にRenderObjectElementを返すWidgetはRenderObjectWidgetという抽象クラスを継承。

状態の更新

ElementとStateはライフサイクルを共にする。StatefulWidgetによって生成されたStatefulElementの場合に限ってはStateと相互参照するようになる.
setStateをすると、StateObjectがdirtyとなり変更の必要があることを認識します。その後、StateObjectが最新化され、Elementに対応するWidgetがbuildされ、再作成されるという流れになります。
この時、Elementツリーは再作成のコストが高いので、可能なものは極力再利用します。

Widgetツリーの更新を効率よくElementツリーに適用できるようにする工夫

  • WidgetがStatelessな軽量オブジェクトである一方、Stateを持つなど生成コストが比較的高めのElementは可能な限り使い回す(Elementとともに生成される重いRenderObjectの再生成を抑えるため、という方が本質的かもしれません)。同一のオブジェクト(参照が同じ)だった場合はそのまま再利用となる。(constおよびキャッシュされているWidgetが該当する)。
  • Widgetツリーの差分をO(N)で検出して、既存Elementの更新・新規作成・破棄を適切に行って最新のWidgetツリーに効率よく追従する

Elementが参照しているwidgetを更新

同じruntimeType・同じKey(不指定の場合もともにnullで条件に合致する)のElementが存在すればそのElementを再利用して新しいWidgetで更新。

setStateの処理

与えられたコールバックを実行と相互参照関係にあるelementのmarkNeedsBuildを実行。
markNeedsBuildはその名の通り、buildの必要性をマーキングした上でリビルドを予約していて、具体的には次の処理となっています。

  • _dirty フラグを立てる
  • BuildOwnerのscheduleBuildForを呼ぶ
  • その中でBuildOwnerの保持する_dirtyElementsにelementが追加される(element更新待ちのキューに入るイメージ)

setStateからElementツリー更新

  • setStateが呼ばれたStateのElement以下のサブツリーを更新しようとする
  • そのサブツリー構築に必要なWidget(軽量オブジェクト)はconstおよびキャッシュされているWidgetを除いてすべて再生成される
  • Widgetを設計図として更新されるElementはできる限り既存のものを流用するようになっていて、かつ差分反映処理はO(N)で済むように考慮されている

https://medium.com/flutter-jp/dive-into-flutter-4add38741d07
https://qiita.com/kazutxt/items/f4a76f35e78c3ec3513c

expsh13expsh13

Singleton(シングルトン)パターン

static(静的)

クラスのメソッドや変数に対して使用される装飾子。
クラス自体に属するため、インスタンスを生成せずに直接呼び出すことができる。
また、インスタンスを生成した場合でも、staticなメソッドや変数は共有される。
https://zenn.dev/tsukatsuka1783/articles/flutter_various_tips#static

factory

factoryコンストラクタは常にそのクラスの新しいインスタンスを作成するわけではないコンストラクタを実装する場合に使用します。
ここでは、常に _instance を返すことで、同じインスタンスが返されるようにしています。これにより、SettingInfo クラスのインスタンスが一つだけであることを保証します。

class SettingInfo {
  late String language;
  late bool isDarkModeEnabled;
  late int fontSize;

  static final SettingInfo _instance = SettingInfo._internal();

  factory SettingInfo() {
    return _instance;
  }

  SettingInfo._internal() {
    language = "ja";
    isDarkModeEnabled = false;
    fontSize = 16;
  }
}
  1. インスタンスの作成
  2. クラスの最初のアクセス時に静的インスタンス _instance が初期化される
  • Dartでは、クラスの静的メンバーはそのクラスが初めて使用された時点で初期化されます。
  • static final SettingInfo _instance = SettingInfo._internal(); により、クラスが初めて参照された時に _internal プライベートコンストラクタが呼び出され、_instance が生成されます。
  1. ファクトリコンストラクタ SettingInfo() が呼び出される