はじめに
前回の記事では、Room × Flow × ViewModel を使ったデータ構造とDB設計について整理しました。
今回はその続きとして、習慣の登録画面と編集画面のUI設計について紹介します。
一見似ている2つの画面ですが、
- 完全に分けて実装するのか
- どこまで共通化するのか
- 差分はどこで吸収するのか
など実装しながら修正し完成しました。
今回の前提構成
- Jetpack Compose
- ViewModelで状態管理
- 登録画面・編集画面は別Composable
- 共通UIは
HabitFormに集約
構成イメージは以下の通りです。
HabitRegisterScreen
│
└── HabitForm(共通UI)
│
HabitEditScreen
└── HabitForm(共通UI + 差分UI)
登録画面

編集画面

なぜ「登録」と「編集」を分けたのか
登録画面と編集画面はUIが似ていますが、責務は異なります。
- 登録画面
- 新規習慣の入力(追加専用のシンプルな画面)
- 編集画面
- 既存データの更新
- 習慣自自体の一時停止・削除や編集
そのため、画面自体は分けつつ、入力フォームだけを共通化する方針を取りました。
共通UIとしての HabitForm
HabitForm は「入力UIを表示すること」だけに責務を絞っています。
例えば、以下登録画面の例になります。
- 習慣名
- カテゴリアイコン
- 開始・終了時間
- 曜日選択
- メモ入力
- 保存ボタン
そして、状態の更新や保存処理は、すべて コールバック経由で外に委譲しています。
@Composable
fun HabitForm(
habit: HabitUiModel,
saveButtonText: String,
onSave: (HabitUiModel) -> Unit,
bottomContent: (@Composable () -> Unit)? = null
)
この設計により、HabitForm 自体は「どの画面で使われているか」を意識しません。
これにより、UIは「状態を受け取って表示するだけ」の存在になります。
画面差分は bottomContent で定義する
編集画面では、以下 登録画面には存在しないUI が必要になります。
- 一時停止期間の設定
- 削除ボタン
条件分岐でUIを切り替えるのではなく、「Composableをそのまま差し込む」形にしたのがポイントです。
HabitForm(
habit = habit,
saveButtonText = "保存する",
onSave = onSave,
bottomContent = {
// 編集画面専用UI
SuspendSetting()
DeleteButton()
}
)
この形にすることで以下メリットがあります。
HabitFormが肥大化しない- 画面ごとの差分が明確
- 将来の拡張もしやすい
UI状態とViewModel状態の切り分け
曜日選択や時間ピッカーなど、一時的なUI操作の状態は HabitForm 側で保持しています。
一方で、最終的に保存される値はすべて ViewModel コールバックで通知します。
onDayOfWeekChange(selectedDays)
onStartTimeChange(updatedStartTime)
これらをすることでComposeとViewModelで役割が明確になります。
- ViewModelはビジネスロジックに集中
- ComposableはUI表現に集中
登録画面での利用例
HabitRegisterScreen {
HabitForm(
habit = habit,
saveButtonText = "登録する",
onSave = onSave
)
}
編集画面での利用例
HabitEditScreen {
HabitForm(
habit = habit,
saveButtonText = "保存する",
onSave = onSave,
bottomContent = {
// 編集画面で使用するフォーム度を配置
}
)
}
bottomContentを使用することで同じフォームを使いながら、画面の責務は明確に分離できています。
まとめ
今回のポイントは以下です。
| 観点 | 方針 |
|---|---|
| 画面構成 | 登録・編集は分離 |
| 共通UI | HabitFormに集約 |
| 差分UI(編集画面特有の機能) | bottomContentで注入 |
| 状態管理 | UI状態とViewModel状態を分離 |
Composeでは「どこまで共通化するか」を意識して組み立てました。
また、Composableの責務を小さく保つことで、より役割を分担できたかと思います。
次回は、登録・編集画面それぞれのViewModel 設計と状態管理についてまとめます。
UIの共通化までは比較的スムーズに進みましたが、「状態をどこで持つか」でかなり悩みました。
次回はそのあたりの試行錯誤や、最終的にどう整理したのかを、
実装ベースで深掘りしていく予定です。

