5738 words
29 minutes
Flutterアーキテクチャについて

概要#

Flutter は「すべてがウィジェット(everything is a widget)」という思想のもと、宣言的・リアクティブに UI を構築するクロスプラットフォーム UI ツールキットである。 普段は ColumnContainer を組み合わせるだけで画面が作れてしまうが、「なぜ width: 100 を指定しても 100px にならないのか」「ホットリロードが効かないのはなぜか」「RenderFlex overflow は何が起きているのか」といった疑問にぶつかったとき、その裏側の仕組みを知っているかどうかで対応力が大きく変わる。

この記事では、Flutter 公式ドキュメントを要約しつつ、以下のトピックを具体例を交えて整理する。

  • Flutter のアーキテクチャ全体像(レイヤー構造 / 3 つのツリー / レンダリングパイプライン)
  • Flutter 内部の仕組み(なぜ大量のウィジェットでも高速なのか)
  • レイアウトの黄金ルール(Constraints go down. Sizes go up. Parent sets position.)
  • ホットリロードの仕組みと効かないケース
  • ビルドモード(debug / profile / release)
  • ドットショートハンド構文(Dart 3.10 / Flutter 3.44)

最後に、これらを段階的に理解していくための学習ロードマップを示す。

本記事は Flutter 3.44 時点の公式ドキュメントを参照している。

💻 前提環境#

  • Flutter: 3.44 系を想定
  • 言語: Dart 3.10 以降
  • 対象読者: Flutter を触り始めた初心者〜中級者
  • サンプルコードは説明用の簡略版

🏛️ Flutter のアーキテクチャ全体像#

レイヤーモデル(Framework / Engine / Embedder)#

Flutter は 拡張可能でレイヤー化されたシステム として設計されている。各レイヤーは下位レイヤーに依存するが、下位レイヤーへの特権的アクセスは持たず、フレームワーク部分はすべて 任意で置き換え可能 になっている。

レイヤー主な言語役割
FrameworkDart開発者が普段触る層。ウィジェット / レイアウト / アニメーション / Material・Cupertino
EngineC++合成済みシーンのラスタライズ、テキストレイアウト、I/O、Dart ランタイム。dart:ui 経由で公開
EmbedderJava/C++(Android), Swift/ObjC(iOS/macOS), C++(Win/Linux)OS との橋渡し。エントリポイント / レンダリング面 / 入力 / イベントループ
flowchart TB
  subgraph framework [Framework Dart]
    materialCupertino[Material / Cupertino]
    widgets[Widgets]
    rendering[Rendering]
    foundation[Foundation / Animation / Painting / Gestures]
    materialCupertino --> widgets --> rendering --> foundation
  end
  subgraph engine [Engine C++]
    dartui[dart:ui]
    impeller[Impeller / Skia / Dart Runtime]
    dartui --> impeller
  end
  subgraph embedder [Embedder Platform]
    os[Android / iOS / Web / Desktop]
  end
  framework --> engine --> embedder

Framework 層をさらに下から見ると次のように積み上がっている。

  • Foundation: 基礎クラス、アニメーション・描画・ジェスチャなどの共通抽象
  • Rendering: レイアウトを扱う層。レンダリング可能オブジェクトのツリーを構築する
  • Widgets: 合成の抽象化層。リアクティブなプログラミングモデルはここで導入される
  • Material / Cupertino: ウィジェット層の合成プリミティブを使って各デザイン言語のコントロールを実装

Framework 自体は比較的小さく、camerawebview などの多くの高機能はパッケージ(プラグイン)として提供される。

アプリの構成要素#

flutter create で生成される標準的なアプリは、以下のピースで構成される。

  • Dart App: ウィジェットを組み合わせて UI とビジネスロジックを実装(アプリ開発者が所有)
  • Framework: ウィジェットツリーをシーンに合成する高レベル API
  • Engine: 合成済みシーンのラスタライズ。dart:ui で公開し、Embedder API で各プラットフォームと統合
  • Embedder: OS とのやり取り、イベントループ管理
  • Runner: Embedder のプラットフォーム固有 API を組み合わせ、実行可能なアプリパッケージにする

リアクティブ UI: UI = f(state)#

Flutter は React に影響を受けた リアクティブ / 宣言的 UI フレームワークである。 開発者は「アプリの状態 → UI の対応」を記述するだけで、状態が変わったときの UI 更新はフレームワークが担う。

ウィジェットは build() メソッドで自身の UI を宣言する。これは状態を UI に変換する関数だと考えられる。

UI = f(state)

build()副作用がなく高速 であることが前提で、フレームワークは必要に応じて(最悪フレームごとに)何度でも呼び出せる。

ウィジェットと「合成(Composition)」#

Flutter の最大の特徴のひとつが 積極的な合成(aggressive composability) である。 たとえば「余白」は他ウィジェットのプロパティではなく、Padding という独立したウィジェットになっている。中央寄せも Align プロパティをいじるのではなく Center ウィジェットで包む。

// 中央寄せは「プロパティ」ではなく「ウィジェットで包む」
Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: const Text('Hello World'),
),
)

クラス階層をあえて 浅く広く することで、小さな単機能ウィジェットの組み合わせ爆発によって表現力を確保している。 よく使う Container でさえ、内部的には LimitedBox / ConstrainedBox / Align / Padding / DecoratedBox / Transform といったウィジェットの合成で作られている。

3 つのツリー(Widget / Element / RenderObject)#

Flutter を理解するうえで最重要なのが、3 種類のツリー が連携して動く点である。

flowchart LR
  subgraph w [Widget Tree]
    w1["不変の設定値<br/>毎フレーム作り直し可"]
  end
  subgraph e [Element Tree]
    e1["ツリー上の位置と実体<br/>State を保持・永続的"]
  end
  subgraph r [RenderObject Tree]
    r1["レイアウト・描画<br/>ヒットテスト"]
  end
  w -->|inflate| e -->|生成・更新| r
ツリー性質役割
Widget不変(immutable)UI の「設定」。build() が毎回新しく返す
Element永続的ウィジェットの「実体」。ツリー上の位置と State を保持
RenderObject永続的実際のレイアウト・描画・ヒットテストを担当

ポイントは Widget は使い捨て、Element は再利用される という点。 Text('A')Text('B') に変えると新しい Widget オブジェクトが作られるが、Element ツリーはフレームをまたいで維持され、変更があった部分だけを差分更新する。これにより「ウィジェットツリーは完全に使い捨てに見えるが、内部表現はキャッシュされている」という設計が成立する。

Element には 2 種類ある。

  • ComponentElement: 他の Element を保持するホスト
  • RenderObjectElement: レイアウトや描画フェーズに参加する Element(対応する RenderObject の仲介役)

build() の引数 BuildContext は、実体としてはこの Element への参照(ツリー上の位置のハンドル)であり、Theme.of(context) のような呼び出しはこの context を使って祖先を辿っている。

状態管理(State / InheritedWidget)#

  • 状態を持たないウィジェットは StatelessWidget
  • 状態を持つウィジェットは StatefulWidget。ウィジェット自体は不変なので、可変状態は別の State クラスに保持する
  • 状態を変えるときは setState() を呼び、フレームワークに再 build を依頼する

ツリーが深くなると、状態をバケツリレーするのが煩雑になる。そこで InheritedWidget を使うと、共通の祖先からデータを効率よく取得できる。

// 祖先に置いた状態を、子孫から型で取得する
final studentState = StudentState.of(context);

Flutter 自身もテーマ(Theme.of(context))などで InheritedWidget を多用している。provider などの状態管理パッケージも、その多くは InheritedWidget のラッパーである。

レンダリングパイプライン(Build → Layout → Paint)#

UI コードが実際のピクセルになるまでの流れは次の通り。

flowchart LR
  state[State] -->|build| widget[Widget Tree]
  widget -->|inflate| element[Element Tree]
  element -->|create/update| render[RenderObject Tree]
  render -->|layout| size[サイズ・位置確定]
  size -->|paint| scene[Scene]
  scene -->|compositeFrame| gpu[GPU でラスタライズ]
  1. Build: 状態から Widget ツリーを構築し、対応する Element ツリーへ展開(inflate)。ここで ContainerColoredBox を挿入するなど、コードより深いツリーになることがある
  2. Layout: RenderObject ツリーを深さ優先で 1 パス走査。親から子へ制約(constraints)を渡し、子から親へサイズ(geometry)を返す。O(n) で完了する
  3. Paint: 各 RenderObjectpaint() で描画。ルートの RenderViewcompositeFrame() でシーンを合成し、dart:ui 経由で GPU に渡す

なお Flutter は OS のウィジェットを使わず、自前のウィジェットセットを直接描画する(描画は Impeller)。これにより OS バージョンによらず同じ見た目・性能を保ち、Flutter ↔ プラットフォーム間の往復オーバーヘッドを避けている。

⚙️ Flutter 内部の仕組み(なぜ高速なのか)#

積極的な合成の結果、Flutter の UI は 大量のウィジェット で構成される。それでも高速に動くのは、レイアウトとビルドに サブリニア(sublinear)なアルゴリズム を採用しているためである。

サブリニアなレイアウト#

  • Flutter のレイアウトは 1 フレームにつき 1 回・単一パス で行われる
  • 親が子の layout を呼んで制約を渡し、子はサイズを返す。一度返した RenderObject はそのフレームでは原則 二度と訪問されない
  • RenderObject はレイアウト中に 最大 2 回(下りと上り)しか訪問されない

さらに、無駄な再計算を避ける仕組みがある。

  • 子が dirty でなく、前回と 同じ制約 を受け取ったなら、子は即座に return できる(走査を打ち切る)
  • 親が子のサイズを使わないなら、子のサイズが変わっても親は再レイアウト不要
  • tight 制約(最小 = 最大)なら、子は親から新しい制約が来ない限りサイズを変えられないので、親の再計算も不要

結果として、dirty なノードとその周辺の限定的な部分木だけが訪問される。

サブリニアなウィジェットビルド#

  • ビルド後のウィジェットは Element ツリーが保持する(ウィジェットは不変で親子関係を覚えられないため)
  • setState() などで Element が dirty になると、フレームワークは dirty な Element のリストを持ち、ビルド時にそこへ直接ジャンプして clean な Element を飛ばす
  • ビルド中は情報が 下方向にしか流れない ため、各 Element はビルド時に最大 1 回しか訪問されない

ここで効くのが const と「再投影(reprojection)」パターン。 ウィジェットが不変なので、親が 同一インスタンス の子で再ビルドすれば、Element はオブジェクト同一性の比較だけで「変わっていない」と判断し、即座に return できる。

// const を付けると同一インスタンスとして扱われ、再ビルドがスキップされやすい
const SizedBox(height: 20)

また、InheritedWidget は各 Element にハッシュテーブルとして情報を 下方向にプッシュ することで、テーマ参照などのたびに親チェーンを辿る O(N²) を回避している。

線形の差分(reconciliation)と key#

意外なことに、Flutter は ツリー差分(tree-diffing)アルゴリズムを使っていない。 代わりに、各 Element の子リストを個別に O(N) で照合する。最適化されるケースは以下。

  • 旧子リストが空
  • 新旧リストが同一
  • 1 か所だけ挿入・削除された
  • 同じ key を持つウィジェット同士をマッチさせる

key を付けると、リスト内で要素が並び替わっても「同じものだ」と認識でき、State を取り違えずに済む。

ツリーサージェリー(GlobalKey)#

Element の再利用は、State と RenderObject を保持できる ため性能上きわめて重要。 GlobalKey を使うと、Element ツリーの 別の場所へウィジェットを移動 しても、新規生成ではなく既存 Element を付け替え(reparent)て部分木全体を維持できる。

これは Hero アニメーション やナビゲーションで多用される。移動先の親が同じ制約を渡せば、子はレイアウトを即 return できるため、移動してもレイアウト情報が再利用される。

無限スクロール(Viewport / Sliver)#

ListView のような無限スクロールは、表示されているぶんだけウィジェットをオンデマンド生成する。

  • スクロール領域の外側は Viewport(「中身が外より大きい箱」)
  • その子は RenderBox ではなく RenderSliver(ビューポートを意識したレイアウトプロトコルを持つ)
  • Sliver は「見えている残り空間」の情報を受け取れるので、無限の子を持ちうる場合でも 有限個だけ 生成する

これを成立させるため、Flutter は ビルドとレイアウトのフェーズを織り交ぜる。レイアウト中(=見える空間が分かった時点)に、その配下のウィジェットを必要なぶんだけ生成できる。

Element と RenderObject を分ける理由#

両ツリーはほぼ同型(RenderObject ツリーは Element ツリーの部分集合)だが、あえて分けている。

  • 性能: レイアウト変更時、合成由来の余計なノードを飛ばして必要部分だけ走査できる
  • 明確さ: ウィジェットのプロトコルと描画のプロトコルをそれぞれ専門化でき、API が単純になる
  • 型安全: RenderObject ツリーは座標系ごとに型を保証できる

📐 レイアウトの黄金ルール#

Flutter のレイアウトでつまずく多くの原因は、HTML/CSS の感覚を持ち込んでしまうこと。 公式は次のルールを 早い段階で暗記すべき としている。

Constraints go down. Sizes go up. Parent sets position. (制約は下りていく。サイズは上っていく。位置は親が決める。)

詳細にすると次の流れ。

  1. ウィジェットは親から 制約(constraints) を受け取る。制約とは「最小・最大の幅」「最小・最大の高さ」の 4 つの double
  2. ウィジェットは各子に制約を伝え、子に「どんなサイズになりたいか」を尋ねる
  3. ウィジェットは子の 位置 を決める
  4. 最後に、ウィジェットは自分の サイズ を親に伝える(受け取った制約の範囲内で)

親子の「交渉」の具体例#

公式の例(幅 300・高さ 85 の制約、5px の padding 付き Column)を要約すると、こうした会話になる。

  • 親「君は幅 0〜300px、高さ 0〜85px の範囲ね」
  • ウィジェット「padding 5px ぶん引くので、子には最大 290 × 75 を渡そう」
  • 第 1 子「では幅 290px、高さ 20px にします」
  • ウィジェット「2 つ目は第 1 子の下に置くから、残り高さは 55px」
  • 第 2 子「幅 140px、高さ 30px にします」
  • ウィジェット「位置は第 1 子 (5,5)、第 2 子 (80,25)。自分は 300×60 にする」と親へ報告

つまり 位置は子自身では決められず、親が決める。子は与えられた制約の範囲でしかサイズを選べない。

ボックスの 3 つのタイプ#

制約の扱い方で、ボックスは大きく 3 種類に分かれる。

タイプ振る舞い
できるだけ大きくなる与えられた制約いっぱいに広がるCenter, ListView
子と同じサイズになる子に合わせるTransform, Opacity
特定サイズになろうとする固有のサイズを持つImage, Text

Container は引数次第で変化する。デフォルトは「できるだけ大きく」だが、width を与えるとそのサイズを尊重しようとする。

これが「width: 100 を指定したのに 100px にならない」問題の正体。 たとえば画面いっぱいの制約を受けた Container(width: 100) は、親(画面)の tight な制約 に従って画面サイズになってしまう。Center で包むと、Center が子に「0〜画面サイズ」の loose な制約 を渡すため、ようやく 100px になれる。

// これは画面いっぱいになる(親が tight な制約を渡すため width が無視される)
Container(width: 100, height: 100, color: Colors.red)
// Center で包むと loose な制約になり、100x100 になれる
Center(
child: Container(width: 100, height: 100, color: Colors.red),
)

tight 制約と loose 制約#

  • tight(きつい)制約: 最小 = 最大。満たすサイズはただ 1 つ。例: 画面ルートが子に渡す制約
  • loose(ゆるい)制約: 最小が 0。「最大までなら好きなサイズで良い」。例: Center が子に渡す制約

ConstrainedBoxUnconstrainedBox の違いも理解の助けになる。

  • ConstrainedBox: 親から受けた制約に 追加の制約 を課す。親が tight ならその追加は効かないことがある
  • UnconstrainedBox: 子に制約を課さず、子が好きなサイズになれる。ただし子が大きすぎると、あの overflow 警告 が出る
// Row の中で子が大きすぎると "RenderFlex overflow" になる
Row(
children: [
Container(width: 4000, color: Colors.blue), // 画面幅を超えて溢れる
],
)

Row / Column のオーバーフローは、「子に制約を課さない」性質と「画面という有限の制約」がぶつかった結果である。ExpandedFlexible で残り空間を分配したり、SingleChildScrollView でスクロール可能にしたりして解決する。

🔥 ホットリロードの仕組み#

ホットリロードは、更新したソースコードを実行中の Dart ランタイムへ注入 する機能。クラスのフィールドや関数を新しい版に差し替えたあと、Flutter フレームワークがウィジェットツリーを自動的に再ビルドし、変更を即座に確認できる。

仕組み#

  1. ホスト側が前回のコンパイル以降に編集されたコードを調べ、変更されたライブラリ・アプリのメインライブラリ・そこに至るライブラリを再コンパイルする
  2. 生成された kernel ファイルをデバイスの Dart VM に送る
  3. Dart VM が新しい kernel からライブラリを再ロードする(この時点ではまだコードは再実行されない)
  4. フレームワークが既存の全ウィジェット / RenderObject の再ビルド・再レイアウト・再描画を発火する

重要なのは、ホットリロードは既存ウィジェットの再ビルドを引き起こすだけ という点。再ビルドに関わるコードしか再実行されないため、main()initState()再実行されない

State が保持される#

ホットリロードは アプリの状態を保持 する。ログイン後の深い画面で UI を修正してリロードしても、ログインし直さずに変更を確認できる。これが通常は望ましい挙動。

ただし、状態に影響する変更をした場合、「最初から実行したときの状態」とは食い違うことがある。その場合は ホットリスタート(状態を捨てて再起動)が必要になる。

ホットリロードが効かない / 効きにくいケース#

ケース挙動対処
main() / initState() の変更再実行されないため反映されないホットリスタート
グローバル変数・static フィールドの初期化子変更遅延初期化のため再実行されないホットリスタート
enum ↔ 通常クラスの相互変更反映されないホットリスタート
ジェネリック型の宣言変更(A<T>A<T, V>反映されないホットリスタート
ネイティブコード(Kotlin/Swift 等)の変更反映されないフルリスタート(停止して再起動)
コンパイルエラー拒否されエラー表示該当行を修正
アプリが kill された中断される再起動
// 例: ルートウィジェットの build より「下流」の変更はホットリロードで反映される
class Counter extends StatelessWidget {
const Counter({super.key});
@override
Widget build(BuildContext context) {
// ここの文字色やテキストを変えるとホットリロードで即反映される
return const Text('count: 0');
}
}

判断の目安は「変更したコードが、ルートウィジェットの build() より下流で再実行されるか」。下流なら反映される。final bar = foo; のような初期化子の変更は反映されにくいので、const にするか getter(int get bar => foo;)に置き換えると良い。

🏗️ ビルドモード#

Flutter はコンパイル時に 3 つのモードを持つ(加えてテスト用のヘッドレスモード)。開発サイクルのどこにいるかで使い分ける。

モード用途コンパイル主な特徴
debug開発中(ホットリロード)JITassert 有効、サービス拡張有効、デバッグ可能。実行速度より開発速度を最適化
profile性能分析AOTrelease に近いが、トレースと一部サービス拡張(パフォーマンスオーバーレイ等)が有効
release本番リリースAOTassert 無効、デバッグ情報除去、起動・実行・サイズを最適化
Terminal window
flutter run # debug(デフォルト)
flutter run --profile # profile
flutter run --release # release
flutter build apk # release ビルドの生成(ターゲット指定)

ポイント。

  • debug は JIT、profile / release は AOT(事前にネイティブコードへコンパイル)でマシンコードに変換される
  • ホットリロードは debug モードのみ 利用できる
  • profile / release は実機が必要で、エミュレータ / シミュレータでは無効(実機相当の性能が出ないため)
  • Web では debug が dartdevc、release が dart2js(minify + tree shaking)でコンパイルされる

開発は debug、カクつきの原因調査は profile、ストア提出は release、と覚えておけば良い。

✨ ドットショートハンド構文(Dart 3.10 / Flutter 3.44)#

ドットショートハンドは、コンパイラが文脈から型を推論できる場合に、静的メンバ・コンストラクタ・enum 値の型名を省略できる 機能。 Flutter は深くネストしたウィジェットツリーを書くことが多く、Alignment.centerMainAxisAlignment.center のような型名の繰り返しがボイラープレートになりがちだった。これを .center のように短く書ける。

Before / After#

// Before(型名を明示)
Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Hello World', style: TextStyle(fontWeight: FontWeight.bold)),
],
),
);
// After(ドットショートハンド)
Container(
alignment: .center, // Alignment.center
padding: const .all(16.0), // EdgeInsets.all(16.0)
child: Column(
mainAxisAlignment: .center, // MainAxisAlignment.center
crossAxisAlignment: .start, // CrossAxisAlignment.start
children: [
Text('Hello World', style: TextStyle(fontWeight: .bold)), // FontWeight.bold
],
),
);

使える場所#

コンパイラが明確な「コンテキスト型」を持つ場所、つまりウィジェットのプロパティ指定のほぼすべてで使える。

  • enum: MainAxisAlignment, CrossAxisAlignment, BoxFit, TextDirection など
  • 静的プロパティ / メソッド: FontWeight.bold.bold など
  • コンストラクタ: EdgeInsets.all().all()BorderRadius.circular().circular()
// 名前付きコンストラクタ(基底クラス EdgeInsetsGeometry を推論)
Padding(
padding: .symmetric(horizontal: 16.0, vertical: 8.0),
child: Text('Spaced out text'),
)
// .new で無名コンストラクタも呼べる(ウィジェットツリーでは稀)
final ScrollController _scrollController = .new(); // ScrollController()

型名が省けるぶん可読性が上がる一方、文脈が曖昧だと推論が効かない。慣れるまでは「何の型が推論されているか」を意識しておくと安全。

🗺️ 理解を深めるためのロードマップ#

ここまでの内容を、手を動かしながら段階的に深めていくためのロードマップを示す。各フェーズの「課題」を実際に試すことで、ドキュメントの知識が腑に落ちる。

flowchart TB
  p1["Phase 1: 入門<br/>ウィジェットで画面を作る"]
  p2["Phase 2: 基礎<br/>レイアウトと状態管理"]
  p3["Phase 3: 内部理解<br/>3 ツリーとパイプライン"]
  p4["Phase 4: 実践<br/>性能・最適化・運用"]
  p1 --> p2 --> p3 --> p4

Phase 1: 入門 — ウィジェットで画面を作る#

項目手を動かす課題対応ドキュメント
Stateless / Stateful の違いカウンタアプリを setState で実装architectural-overview
合成(composition)Center + Padding + Column で簡単な画面を組むarchitectural-overview
ホットリロード体験色やテキストを変えて即反映を確認、main() 変更が効かないことも体験hot-reload

ゴール: 「すべてがウィジェット」「UI = f(state)」を体感する。

Phase 2: 基礎 — レイアウトと状態管理#

項目手を動かす課題対応ドキュメント
レイアウトの黄金ルールContainer(width:100) が画面いっぱいになる→Center で 100px になる、を再現constraints
tight / loose 制約ConstrainedBoxUnconstrainedBox の違いを確認constraints
overflow の解消Row をわざと溢れさせ、Expanded / SingleChildScrollView で直すconstraints
状態の受け渡しInheritedWidget(または provider)でテーマや状態を共有architectural-overview

ゴール: 「Constraints go down. Sizes go up. Parent sets position.」を説明できる。

Phase 3: 内部理解 — 3 つのツリーとパイプライン#

項目手を動かす課題対応ドキュメント
Widget / Element / RenderObjectFlutter DevTools の Inspector で実際のツリーの深さを観察architectural-overview / inside-flutter
key の役割リストの並び替えで State が崩れる例を作り、key で直すinside-flutter
GlobalKey とツリーサージェリーHero アニメーションを実装し、State 保持を確認inside-flutter
const の効果const の有無で再ビルド回数が変わることを確認inside-flutter
無限スクロールListView.builder で大量データを表示し、Sliver の遅延生成を理解inside-flutter

ゴール: 「なぜ大量のウィジェットでも高速なのか(サブリニア / 線形差分)」を説明できる。

Phase 4: 実践 — 性能・最適化・運用#

項目手を動かす課題対応ドキュメント
ビルドモードの使い分けdebug / profile / release を切り替えて性能差を体感build-modes
性能分析profile モードで DevTools の Performance を見て jank を調査build-modes
RepaintBoundary再描画範囲を絞って描画コストを下げるinside-flutter
ドットショートハンド既存コードを .center 等で書き換えて可読性を改善dot-shorthands
プラットフォーム連携Platform Channel / FFI でネイティブ機能を呼ぶarchitectural-overview

ゴール: 性能問題を計測ベースで切り分け、適切な最適化を選べる。

📚 参考リンク(公式ドキュメント)#

Flutterアーキテクチャについて
https://tutttuwi.github.io/posts/2026-05-31_flutterアーキテクチャについて/
Author
Tomoaki Tsutsui
Published at
2026-05-31
License
CC BY-NC-SA 4.0