【習慣トラッカー開発記 #9】カテゴリー選択画面の実装 — 画面遷移とデータ連携について

はじめに

前回の記事では、登録・編集画面の ViewModel 設計と状態管理について紹介しました。

今回は、本画面の設計において、特に意識した「状態をどこに持たせるか」と「画面の責務をどこまでにするか」の2点についてまとめます。

状態は一貫して ViewModel に集約する

画面・編集画面では、以下の保存対象となる状態をすべて ViewModel が保持しています。

  • 選択されたカテゴリ
  • サブカテゴリ
  • アイコンや表示名

一方で、カテゴリー選択画面・サブカテゴリー選択画面は、

  • 一覧を表示する
  • ユーザー操作を受け取る
  • 選択結果を呼び出し元に返す

という一時的なUIに徹し、選択結果を画面自身では保持しません。

これにより、以下のようなメリットがあります。

  • 「今どこが状態を持っているのか」が常に明確
  • 画面回転や再生成にも強い
  • 登録・編集で設計を揃えやすい

画面遷移と状態更新を混ぜない

カテゴリ選択結果の受け渡しには、NavControllersavedStateHandle を使用しています。

navController.previousBackStackEntry
    ?.savedStateHandle
    ?.set(key, selectedCategory)
navController.popBackStack()

この構成では、以下役割分担があります。

  • Navigation は「画面を戻す」だけ
  • 状態の反映は、戻り先の ViewModel が行う

画面遷移に状態を直接乗せないことで、

  • パラメータの肥大化を防げる
  • 編集途中の状態とズレにくい
  • 後から画面構成を変更しやすい

といった利点がありました。

カテゴリ選択画面は「状態を持たないUI」と割り切る

今回のカテゴリ選択画面では、機能は実装していません。

  • 選択中のハイライト表示
  • 選択状態の保持

理由は、あくまでもカテゴリ選択前の一時的な選択となるため、状態を持たせるとViewModelが必要になることから役割を分けています。

また、カテゴリ選択後は「選択前の画面に戻る」という挙動に限定することで、Composableは表示と入力だけに集中できました。

編集機能は UI 状態として実装する

前提としてマスターデータに対しては編集、削除等は実施できないように以下で制御しています。

サブカテゴリー選択画面では、マスターデータを一部用意しつつ、ユーザー作成データとして以下の操作を可能にしています。

  • サブカテゴリ名の追加
  • 編集
  • 削除
// ユーザー作成データであり、かつ編集モードの場合に削除、カテゴリ名の編集可能
val isUserEditable = isEditing && subCategorySource == "user"

// 編集モードだがユーザー作成ではない(マスターデータ)場合に無効化する
val isDisabled = isEditing && subCategorySource != "user"

このように分解することで、以下状態がコードから直感的に読み取れるようになりました。

  • UIの見た目
  • タップ時の挙動
  • 操作可能・不可の制御

登録・編集で分けつつ、無理に抽象化しない

登録画面と編集画面はViewModelを分けていますが、カテゴリ選択画面(状態管理や画面遷移)のロジックは ViewModel、Composableで実装し、データ変換だけを共通化しています。

そのため、

  • ViewModel 同士を直接依存させない
  • Composable に Entity や DB モデルを持ち込まない
  • 画面ごとの責務を肥大化させない

ことを目的に、変換専用の拡張関数を切り出しています。

おわりに

Compose では、UIの共通化、画面分割、状態管理をそれぞれ独立して考える必要がありますが、特に重要だと感じたのは 「その状態は最終的に保存されるのか?」 という視点でした。

この画面では特に状態管理で悩みましたが、次の基準を持って設計することで、画面数が増えても構成が破綻しにくくなったと感じています。

  • 保存される状態 → ViewModel
  • 一時的な選択や入力 → Composable
タイトルとURLをコピーしました