はじめに
Jetpack Compose を学んでいると、必ずぶつかるのが State(状態)。
「なんか remember とか mutableStateOf とかいっぱいあるんだけど⁉」
と感じる人は多い。でも、一度“本質”をつかむと、Compose はめちゃくちゃ気持ちよく書けるようになる。
この記事では、Compose の状態管理をゼロから体系的に解説する。
1. Compose は「宣言的 UI」
まず大前提。
Compose の UI はこう定義できる:
UI = f(state)
UI は「状態」から“自動的に作られる産物”
だから Compose は「UI を直接変更する」必要がない。
状態を変えれば、UI が勝手に変わる。
まるで——
「心の状態を変えたら表情が変わる」
みたいなもの。(ちょっと哲学)
2. State の基本は “mutableStateOf”
Compose で UI 更新をトリガーする最小単位がこれ。
var count by remember { mutableStateOf(0) }
-
mutableStateOf→ 監視可能な状態 -
remember→ 再コンポーズしても値を保持 -
by→ デリゲートでcount++と扱いやすくする
UI はただ状態を読むだけ
Text("Count: $count")
状態を変えると UI が自動で更新
Button(onClick = { count++ }) {
Text("Add")
}
これが Compose 最大の魔法。
しかも魔法と言いつつ、実際はとてもシステマチック。
3. remember と rememberSaveable の違い
| 関数 | 持続範囲 | 用途 |
|---|---|---|
| remember | 再コンポーズ間 | UI の一時状態 |
| rememberSaveable | 再コンポーズ+画面回転も保持 | 入力値やフォーム |
例:
var text by rememberSaveable { mutableStateOf("") }
スマホを回転しても消えない。
つまり、TextField などには rememberSaveable が超適切。
4. State Hoisting(状態の持ち上げ)は必須テク
Compose の公式デザイン哲学:
UI はできるだけ “状態を持たない” 方がいい。
状態は外(ViewModel)に置く方が健全。
例えばダメな例:
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
...
}
状態と UI が混ざる → テストしづらい。
正しい書き方:State Hoisting
@Composable
fun Counter(count: Int, onAdd: () -> Unit) {
Button(onClick = onAdd) { Text("$count") }
}
外部に State を持たせる:
var count by remember { mutableStateOf(0) }
Counter(count, onAdd = { count++ })
UI は純粋関数になる。
Flutter の「状態リフトアップ」もほぼ同じ考え方。
5. ViewModel × StateFlow × Compose の鉄板構成
実プロダクトではほぼこれ一択。
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
private val _uiState = MutableStateFlow(0)
val uiState = _uiState.asStateFlow()
fun add() { _uiState.value++ }
}
Compose 側:
val count by viewModel.uiState.collectAsState()
Counter(count) { viewModel.add() }
完全に“UI とロジック分離”。
Clean Architecture が自然に成立する。
6. Recomposition(再コンポーズ)は怖くない
よく聞かれる質問:
UI 全部作り直してるの?
パフォーマンス大丈夫なの?
→ 心配不要。
Compose は内部で:
- 「どの Composable がどの状態を読んだか」
- 「どこを再コンポーズすればいいか」
を 自動で依存追跡 している。
だから、パフォーマンスは極めて賢く最小限の再描画になる。
7. Snapshot System(Compose の裏側の天才)
mutableStateOf が変わると snapshot が変更され、
Compose は次のように動く:
- 依存している Composable だけを再実行
- 再コンポーズの順序や領域を最適化
- 不要な更新は全て除外
つまり:
「状態が変わったところだけ UI を更新する最適化エンジン」
これが UI を高速・効率的に保っている。
8. State の種類まとめ
| 種類 | 説明 | 使うタイミング |
|---|---|---|
| mutableStateOf | 基本の状態 | 小規模 UI |
| remember | 再コンポーズに耐える | 一時変数 |
| rememberSaveable | 回転 survive | 入力値・フォーム |
| derivedStateOf | 派生値をメモ化 | 高コスト計算 |
| SnapshotFlow | State → Flow | ViewModel 連携 |
| LaunchedEffect | 副作用管理 | API 呼び出しなど |
| SideEffect | UI スレッド副作用 | ログなど |
| DisposableEffect | クリーンアップ用 | リスナー解除 |
9. よくある間違いとベストプラクティス
❌ remember を忘れて毎回初期化
→ TextField の内容が消える
❌ 深い Composable に State を置く
→ ロジックと UI が密結合になる
❌ ViewModel も State もない
→ アプリが育たない構造に
10. まとめ
Compose は:
- UI = 状態の写し鏡
- 状態が変われば UI が自動更新
- remember + mutableStateOf が基本
- 状態は hoist(持ち上げ)て外へ
- ViewModel + StateFlow で最強
- 再コンポーズはスマートで高速
- Snapshot System が裏で全部管理
→ この思想をつかむと Compose が一気に“わかる側”になる。