― 宣言的UIがエンジニアに要求している“別の力” ―
※ChatGPTを使用して記事を作成しています。
Jetpack Compose を触り始めたとき、多くのエンジニアがこう感じます。
「XMLより自由度が高いのに、なぜか雑に書くとすぐ壊れる」
これは偶然ではありません。
Compose は「書きやすいUIツール」ではなく、状態と責務を正しく設計できているかを容赦なく試すフレームワークだからです。
■ 従来の View システムは“多少雑でも動いた”
XML + View の世界では、次のような書き方でも成立していました。
- Activity / Fragment にロジックを書き散らす
- View を直接書き換える
- 状態がどこにあるか曖昧でもとりあえず表示される
- invalidate すれば何とかなる
これは 命令的UI だったためです。
「どう描き直すか」を人間が制御していたので、設計が多少崩れていても“力技”で成立していました。
■ Compose は「状態が正しくないと描けない」
Compose はまったく逆の思想です。
Compose は UI を 状態の結果としてしか描けません。
@Composable
fun UserName(name: String) {
Text(name)
}
ここには「更新処理」は存在しません。
あるのは 状態 → 描画 という一方向の関係だけです。
つまり、
状態設計を間違えると、UIの正しさも即座に崩壊する
これが「雑に書けない」の正体です。
■ Compose が要求しているのは「再現可能性」
Compose が最も重視しているのはこれです。
同じ状態なら、必ず同じUIになること。
これを壊すコードを書くと、次のような不具合が出ます。
- 再コンポーズで値が戻る
- 画面回転で状態が消える
- なぜか UI が更新されない
- LaunchedEffect が暴走する
- remember の位置でバグる
これらはすべて、
状態の出どころが不明確
なときに発生します。
■ 「どこで状態を持つか」を常に問われる
Compose では、開発中ずっとこの問いを突き付けられます。
- この状態は誰の責任?
- 永続?一時?
- UI専用?ドメインデータ?
- 再生成していい?ダメ?
View 時代は意識しなくても済んだ設計判断を、Compose は強制的に顕在化させます。
■ 例:雑に書いた場合の典型的な破綻
@Composable
fun Counter() {
var count = 0
Button(onClick = { count++ }) {
Text("$count")
}
}
一見正しそうに見えますが、これは成立しません。
なぜなら、
countは状態ではない- 再コンポーズで初期化される
- Compose にとって変更が観測不能
Compose は「変更した」という事実を知る術がないのです。
■ 正しい書き方は「状態を観測可能にする」
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("$count")
}
}
ここで初めて、
- 状態の保存場所が明確になり
- 変更が通知され
- UIが再計算可能になる
Compose はこれを 最初から要求している のです。
■ Compose が鍛えているのは「設計の純度」
Compose が難しく感じる理由は API ではありません。
設計のごまかしが効かないからです。
- 状態を混ぜると即バグる
- 責務を跨ぐと再利用不能になる
- 副作用を隠すと破綻する
つまり Compose は、
正しく設計されたコードだけが楽になる
という、非常に正直なフレームワークなのです。
■ 若手ほど「Composeの難しさ」を誤解しやすい
「Compose は難しい」と言われがちですが、本質は違います。
Compose が難しいのではなく、
今まで曖昧でも許されていた設計が許されなくなった
だけです。
■ Compose に慣れるコツは「UIを書こうとしないこと」
Compose を使いこなす人ほど、こう考えています。
UIを書いているのではない。
状態の流れを設計している。
UIはその“結果”に過ぎません。
■ まとめ
「Composeは雑に書けない」
これは欠点ではなく、最大の利点です。
Compose は、
- 状態の責務を明確にし
- 再現可能なUIを作り
- 設計の質を引き上げる
ためのツールです。
もし Compose が書きづらいと感じたら、それは API の問題ではなく、
状態設計を見直すべきタイミング
なのかもしれません。

