mmuulan
FlutterMobile設計思想

Flutterを理解するための設計思想

Created 2026-07-12

目次


概要

このドキュメントは、モバイルやWebの一般的な開発経験はあるけれど、Flutterはこれから、という人に向けたものです。ウィジェットの書き方(Containerのプロパティ一覧や、setStateの文法)を一から教えるものではありません。そうではなく、Flutterがなぜこういう設計になっているのか、その「考え方の根っこ」を15〜30分でつかむことを目的にしています。

なぜ根っこから入るのか。Flutterには「なぜこうなっているの?」と戸惑う特徴がいくつもあります。余白(パディング)すらウィジェットとして書く、画面を手続き的に更新するのではなく状態から丸ごと描き直す、iOSやAndroidの標準ボタンを使わず自前で描く——。これらを一つずつ暗記するのは大変です。でも、おおもとにある一つの発想さえ分かってしまえば、個別の挙動は「ああ、だからこうなるのか」と一本につながります。AIが書いたFlutterコードを見て「これはFlutterらしくないな」と気づけるようになるのも、この根っこを持っているかどうかで決まります。

そしてその根っこは、Flutter自身がスローガンとして掲げている一言に集約されます——すべてはウィジェット。ボタンも、余白も、レイアウトも、アプリ全体さえもウィジェットであり、小さなウィジェットを組み合わせて複雑なUIを作る、という徹底したコンポジション(部品の組み合わせ)がFlutterのUIの根幹です(出典: Flutter architectural overview)。この先の各章は、この一つの発想が場面ごとにどう顔を出すか、という話だと思って読んでください。


そもそもFlutterは何を最優先しているのか

新しいUIフレームワークに触れるとき、多くの人は「どんな部品(コンポーネント)が用意されているか」を気にします。Flutterはそこに、少し変わった答え方をします。「特別な部品はない。すべては同じ"ウィジェット"という一種類の部品であり、それを組み合わせるだけだ」と言うのです。

Flutterのスローガンは「すべてはウィジェット」です。これは単なる標語ではなく、設計の隅々まで貫く方針です。テキストやアイコンのような目に見えるものだけでなく、行や列といった配置のしくみ、さらには余白(パディング)までもがウィジェットです(出典: Layouts in Flutter)。たとえばPaddingは、他のウィジェットが持つ「余白プロパティ」ではなく、それ自体が独立したウィジェットとして存在します(出典: Inside Flutter)。この徹底ぶりを、公式は「アグレッシブなコンポジション(積極的な組み合わせ)」と呼んでいます。

なぜここまで一種類にこだわるのか。部品の種類を増やさず「ウィジェットを組み合わせる」一択に絞ることで、開発者が覚えるべきルールが一つになり、部品同士を自由に組み替えられるからです。複雑なウィジェットは、より単純なウィジェットの組み合わせでできています。たとえばContainerは、PaddingAlignDecoratedBoxといった単機能のウィジェットを組み合わせて作られています(出典: Flutter University)。小さな単機能の部品を積み上げて大きなものを作る——このレゴブロックのような発想が、Flutterのすべての出発点です。

これから先の章は、すべてこの「すべてはウィジェット=組み合わせで作る」という発想が、いろいろな場面でどう形になっているか、という話です。まずこの一本の軸を頭に置いてください。


なぜ「画面を組み立てる」のではなく「状態から描く」のか

ネイティブのモバイル開発を経験した人がFlutterに来ると、UIの更新のしかたに戸惑います。従来のやり方では、「このラベルの文字を書き換える」「このボタンを非表示にする」というように、画面の部品を一つずつ手で操作して更新していました。ところがFlutterには、その発想があまり出てきません。代わりに「今の状態なら画面はこうなる」という記述を書き、状態が変わったら画面を丸ごと描き直させます。

この「状態からUIを導く」考え方を、宣言的UIと呼びます。Flutterのウィジェットは、build()というメソッドの中で「現在の状態を、UIへ変換する」という形で自分の見た目を宣言します(出典: Flutter architectural overview)。開発者は「どう変更するか(手順)」ではなく「どうあるべきか(状態に対する結果)」を書きます。状態が変われば、Flutterが必要な部分を判断して描き直してくれるので、部品を一つずつ手で書き換える必要がありません。

なぜこの方式なのか。これは「すべてはウィジェット」という核の自然な延長です。UIが小さなウィジェットの組み合わせで表現できるなら、状態が変わったときも「新しい状態に対応するウィジェットの組み合わせ」を丸ごと作り直すのが、いちばん素直です。手作業で個々の部品を更新するより、「状態→UI」という一方向の変換に任せるほうが、コンポジションの発想と噛み合います。次章で見るように、Flutterはこの「丸ごと作り直す」を現実的な速度で実現するための工夫を、内部に隠し持っています。


ウィジェットが毎回作り直されても平気なのはなぜか

前章を読むと、当然の不安が浮かびます。「状態が変わるたびにウィジェットを丸ごと作り直すなんて、重すぎないか?」という疑問です。画面の一部が変わるだけで、毎回たくさんのウィジェットを作り直していたら、遅くなりそうに思えます。ところがFlutterはこれを平然とやってのけます。

その秘密は、ウィジェットの正体にあります。Flutterのウィジェットは、画面に描かれた実体ではなく、イミュータブル(作った後は変更できない)な「設定情報」にすぎません(出典: Flutter architectural overview)。Text("Hello")と書いても、それはまだ画面上のピクセルではなく、「こう表示したい」という構成の宣言でしかないのです(出典: DEV Community, Flutter Architecture Explained ※コミュニティ記事)。ウィジェットは作るのも捨てるのも軽い、使い捨ての設計図です。だから大量に作り直しても平気なようにできています。

では実際の描画はどうなっているのか。Flutterは内部で3つの木(ツリー)を使い分けています。開発者が書く「ウィジェットの木(設計図)」、その状態を管理する「エレメントの木」、実際のレイアウトと描画を担う「レンダーオブジェクトの木」です(出典: LogRocket, Flutter architecture ※コミュニティ記事)。状態が変わると、軽いウィジェットの木は作り直されますが、重いレンダーオブジェクトの木は、変わった部分だけが更新されます。つまり「作り直すのは軽い設計図だけ、重い実体は差分だけ触る」という分業です。この仕組みがあるからこそ、「すべてはウィジェット」「丸ごと描き直す」という贅沢な発想を、現実的な速度で成立させられるのです。


OSのボタンを使わず、自前で描くのはなぜか

多くのクロスプラットフォーム(複数のOSで動く)フレームワークは、内部でiOSやAndroidの「本物の」ネイティブUI部品を呼び出して画面を作ります。Flutterはここで大きく違う道を選びました。OSの標準部品を一切使わず、ボタンもテキストも、Flutterが自分で一から描画します。「わざわざ車輪を再発明しているのでは?」と感じるかもしれません。

しかしこれは、Flutterの核と一貫した選択です。「すべてはウィジェット」で「すべてを組み合わせで作る」なら、その最下層である"描画そのもの"まで自分の手に持っておくのが筋が通ります。Flutterはプラットフォームのネイティブ部品に依存せず、独自のエンジン(描画基盤)で画面に直接描きます(出典: Flutter University)。これには明確な利点があります。まず、どのOSでもまったく同じ見た目を再現できます。次に、OSの部品とやり取りする「橋渡し」のコストがなくなるため、性能上も有利です(出典: Flutter University)。

この「自分で全部描く」思想は、前章までの話とも噛み合っています。UIが軽いウィジェットの組み合わせで表現され、状態から丸ごと描き直せて、その描画を自前のエンジンが受け持つ——この一貫した縦のラインがあるからこそ、Flutterは表現の自由度と一貫性を同時に手にしています。逆に言うと、カメラやセンサーのようにOS本来の機能が必要な場面では、この自前主義の外に出る必要があり、そこだけは「プラットフォームチャネル」という橋を渡ってネイティブとやり取りします(出典: DEV Community, Flutter Architecture Explained ※コミュニティ記事)。描画は自前、OS機能は橋渡し、という切り分けも、核を知っていると自然に理解できます。


状態とUIをわざわざ引き離すのはなぜか

小さなアプリを書いているうちは気になりませんが、規模が大きくなると、Flutter公式は「状態(データ)とUI(ウィジェット)をきっぱり分けなさい」と強く勧めてきます。ウィジェットの中に何でも書けてしまうのに、なぜわざわざ引き離すのか、最初は少し過剰に感じるかもしれません。

公式が推奨するのは、UIのロジックとビジネスロジックを層(レイヤー)として分ける「レイヤードアーキテクチャ」です(出典: Common architecture concepts)。そしてその中で、状態はデータ層からロジック層を通ってUIのウィジェットへ、一方向に流れます。ユーザー操作のイベントは逆向きに、UIからロジック層、データ層へと戻っていきます。この一方通行の流れを単方向データフローと呼びます(出典: Common architecture concepts)。データとUIが双方向に絡み合わないので、状態の流れが追いやすくなります。

なぜこの分離が効くのか。ここでも「すべてはウィジェット」が効いています。ウィジェットは組み合わせ可能な軽い設計図であって、ビジネスロジックを抱え込む場所ではない、という前提があるからです。公式は「再利用できる、ロジックをできるだけ持たない軽いウィジェットを書くべき」と述べています(出典: Common architecture concepts)。ウィジェットを「状態を表示するだけの薄い層」に保てば、状態が変わるたびに丸ごと描き直す前章までの仕組みとも綺麗に噛み合いますし、ロジックをウィジェットから切り離せるのでテストもしやすくなります。分離は面倒を増やすためではなく、コンポジションの思想を大きな規模まで壊さず持ち込むための工夫なのです。


この思想をAIコーディングにどう活かすか

ここまでの「すべてはウィジェット=組み合わせで作り、状態から描く」という軸を握っておくと、AIに書かせたFlutterコードが「Flutterらしいかどうか」を、細かいAPIを覚えていなくても見抜けるようになります。AIは膨大なコードを学んでいるぶん、他のUIフレームワークのクセや、古いやり方をそのまま持ち込みがちだからです。よくあるズレを挙げます。

まず、手続き的なUI更新のクセです。「状態が変わったらこの部品を書き換える」といった、命令的に画面をいじるコードが出てきたら要注意です。「状態からUIを導く(宣言的UI)」という軸に照らせば、状態を更新して描き直させる形に直すべきだと判断できます。

次に、ウィジェットへのロジックの詰め込みです。1つのウィジェットの中に、データ取得もビジネスロジックも表示も全部入っているコードは、「ウィジェットは軽い設計図に保つ」「状態とUIを分ける」という軸に反します。ロジックを別の層へ切り出せているかを見ます。

さらに、コンポジションの不足です。巨大な1つのウィジェットにすべてを書き込むのは、「小さな単機能ウィジェットの組み合わせで作る」という核から外れています。適切に小さく分割し、組み合わせているかを確認します。

最後に、重いbuild()メソッドです。build()は状態からUIへの変換であり、何度も呼ばれる前提で、副作用を持たず高速であるべきものです(出典: Flutter architectural overview)。ここに重い処理や副作用を書き込んでいたら、丸ごと描き直す仕組みと相性が悪く、パフォーマンス問題の原因になります。

まとめると、レビューのコツは「このAPIの使い方は正しいか」を一つずつ照合することではありません。もう一段深く、すべてはウィジェットという核に沿っているか——組み合わせで作り、状態から描き、ウィジェットを軽く保てているか、という目で見ることです。この軸さえ持っていれば、細かなAPIをそらで言えなくても、違和感にちゃんと気づけます。


参考文献一覧