綴Photo の勉強ノートに戻る
開発記録2026.04.04

画面ごとのサイズ違いは、表を1枚作って選ぶだけにする — Flutter のデータクラスで isDesktop をウィジェットから追い出した話

Flutterマルチプラットフォームリファクタ綴Photo

綴Photo は、最初 Mobile だけだった。 実装は AI に手伝ってもらいながら進めていて、そこに Desktop 対応を後から足したとき、コードの中に if (isDesktop) が散らかり始めた。 気づいたら、ウィジェットの中身がほぼ「分岐とサイズ計算」だけになっていた。 これを一度立ち止まって、集約する方針に倒してきれいにした、その記録だ。


なぜ困ったか:isDesktop がウィジェットの中に染み出していた

Mobile 対応だけのときは、サイズはほぼ固定値でよかった。 画面幅の差は端末で多少あるけど、それは MediaQuery で吸収できる範囲だった。

でも Desktop を追加した瞬間に世界が変わった。

  • パディング、フォントサイズ、ボタンサイズ、アイコンサイズ……全部 Mobile と Desktop でスケールが違います
  • 同じ「本文サイズ」でも Mobile は 14、Desktop は 16、みたいに値が変わります

最初は素直に、ウィジェットの中で

dart
final bodyFontSize = isDesktop ? 16.0 : 14.0;
final cardPadding = isDesktop ? 24.0 : 16.0;
final iconSize = isDesktop ? 40.0 : 24.0;

みたいになっていた。

数字は各ウィジェットのソースに直書き。DB にも prefs にもファイルにも無い。画面1isDesktop で分岐本文 16 / 14余白 24 / 16アイコン 40 / 24ソースに直書き画面2isDesktop で分岐本文 16 / 14余白 24 / 16アイコン 40 / 24ソースに直書き画面3isDesktop で分岐本文 16 / 14余白 24 / 16アイコン 40 / 24ソースに直書き画面4isDesktop で分岐本文 16 / 14余白 24 / 16アイコン 40 / 24ソースに直書き画面5isDesktop で分岐本文 16 / 14余白 24 / 16アイコン 40 / 24ソースに直書き同じ判定が5画面にコピーされている。直す場所が散らばる。

Desktop の数字を変えたいときに、直す場所が散らかっていて見つからない。

「これ、サイズの一覧表が1個欲しいんだな」と気づいて、データクラスに集約する方針に倒した。コードを書く手は AI に借りていても、この「散らかってきたから1箇所に寄せる」という設計の判断は、自分が散らかりに気づかないと始まらなかった。


解決:データクラスに Mobile / Desktop のプリセットを持たせる

最終的にこういう形に落ち着いた。

サイズ定義を全部1個のデータクラスにまとめて、Mobile 用と Desktop 用の値セットを static const のプリセットとして2つ持たせる。

dart
class AppDimensions {
  final double cardPadding;
  final double bodyFontSize;
  final double contentMaxWidth;
  // ... ほかにもたくさん
 
  // private な単一コンストラクタ。外から直接呼ばせず、下のプリセット専用にする
  const AppDimensions._({
    required this.cardPadding,
    required this.bodyFontSize,
    required this.contentMaxWidth,
  });
 
  // Mobile 用・Desktop 用の値を固定値で詰めた static const プリセット
  static const mobile = AppDimensions._(
    cardPadding: 16, bodyFontSize: 14, contentMaxWidth: 560,
  );
  static const desktop = AppDimensions._(
    cardPadding: 24, bodyFontSize: 16, contentMaxWidth: 680,
  );
}

呼び出し側は

dart
final dimensions = isDesktop ? AppDimensions.desktop : AppDimensions.mobile;
数字は AppDimensions の static const。実行時メモリの定数で、保存しない。同じ型のプリセットが2つAppDimensions.mobile本文14・余白16・幅560コードに焼き込み。保存しないAppDimensions.desktop本文16・余白24・幅680コードに焼き込み。保存しない交差点 画面の上で1回選ぶisDesktop で mobile か desktop か。分岐はここだけ各ウィジェット dimensions.cardPadding を読む選んだ1セットは画面のメモリ。保存しない。分岐は持たない

Desktop の数字を1個変えたいときの修正範囲が 「データクラスの該当行だけ」になった。

※ この記事では「画面の上の方で1回だけ isDesktop で選ぶ」と書いているが、実際の綴Photo では後にこの選択を AppDimensionsProvider(MaterialApp の builder 内に置いた供給役)に寄せて、画面幅から自動判定するようにした。「データクラスに集約する」考え方は同じで、選ぶ場所が1段ツリーの上に移った形。その「ツリーに流す」仕組みは別記事(デザイントークン)で書いている。




「static const プリセット」って何がうれしいか

このやり方の肝は、Mobile 用・Desktop 用を 同じ AppDimensions 型の static const 定数として持っておくところにあります。

なんでこうしたかというと、

  • Mobile と Desktop は 同じ「サイズ定義」という抽象を表しています
  • でも値は違います
  • 「Mobile 用のサイズ」「Desktop 用のサイズ」を、別の型ではなく同じ型の2つの定数として表現したい

別クラスに分けてもいいんだけど、呼び出し側の型は揃えたい。 AppDimensions.mobile / AppDimensions.desktop という2つの static const にすると、それが自然に表現できます。

dart
AppDimensions dimensions = AppDimensions.mobile;   // 同じ型
AppDimensions dimensions = AppDimensions.desktop;  // 同じ型

ここに分岐の責任を寄せておくと、コードの他の場所が驚くほど静かになります。



この設計のうれしい副作用

データクラスにまとめた副作用として、以下が嬉しかった。

(1) const にできる

AppDimensions.mobile / AppDimensions.desktop は完全に固定値なので static const にできます。 これは Flutter のリビルド最適化に効きます(同じ定数は同一インスタンスとして扱われます)。

(2) テストが書きやすい

「Desktop の本文サイズはいくつ?」を、 ウィジェットを起動せずに AppDimensions.desktop.bodyFontSize で確認できます。 これは個人開発でも結構助かった。

(3) Desktop 用の数字を変えるときの怖さが消えた

修正範囲が1ファイルだから、「他にも触らないといけない場所があるかも」という不安がない。 個人開発、こういう「不安が減る」設計はメンタルにめちゃくちゃ効きます。


この上で、もう一段だけ整理した話

このデータクラスを「どこで作って、どうやって各ウィジェットに届けるか」も整理した。

素朴にやると「画面の一番上で dimensions を作って、子ウィジェットに props で渡し続ける」になるが、ネストが深くなると dimensions がバケツリレー状態になります。

なので綴Photo では、この値を InheritedWidget(AppDimensions.of(context) で取れる仕組み)でツリーに流しています。実コードでは最初からこの形で入れてあって、データクラスに集約する話とセットで成立しています。この「ツリーに流す」側の仕組みは「デザイントークン」というテーマで別記事に分けて書いた。


学んだこと

  • if (isDesktop) を書きたくなったら、判定を1箇所に寄せられないかを先に考えます
  • 「サイズ定義」みたいな地味なものほど、データクラスにまとめる効果が大きいです
  • 同じ型の static const プリセットを2つ持つと「同じ型・違う中身」を素直に表現できます
  • 修正範囲が狭まると、開発中の不安そのものが減ります

ウィジェットからプラットフォーム判定が消えると、コードを読む速度が上がります。 「このウィジェットは何をしているか」だけに集中できます。

個人開発では特に、自分が後で読みます。だからこの「読みやすさ」は他人事じゃないです。


この記事は2026年4月時点の自分の理解と実装方針で書いています。アプリは継続開発中なので、現在の実装と細部が違っている可能性があります。