はじめに
前回は、アーキテクチャ選定(MVVM + Repository + Hilt + Flow)について紹介しました。今回は、アプリの中核となる データベース構成(Room) と エンティティ設計 のポイントを整理します。
この記事の目的
- データモデルをどう整理したか
 - Entity 、View 、 Repository の責務分け
 - 今後の拡張を見据えたテーブル設計の考え方
 
全体構成イメージ
アプリでは「習慣(Habit)」を中心にカテゴリ構造を持たせて整理しています。
CategoryMaster(大分類) ─┬─ SubCategory(小分類)
                            │
                            └─ Habit(ユーザーごとの習慣データ)
たとえば:
- CategoryMaster:運動・メンタル・食事など
 - SubCategory:「ランニング」「瞑想」「朝食をとる」など
 - Habit:ユーザーが登録した実際の習慣(ON/OFFや曜日情報を保持)
 
Habitエンティティ
アプリの中核データです。以下は1件の「習慣」を表すEntityです。
@Entity(tableName = "habit")
data class Habit(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val subCategoryId: Long,
    val description: String?,
    val enable: Boolean = false,
    val dayOfWeek: String, // 例: "1,3,5" → 月・水・金
    val updatedAt: Long
)
ここでは以下を意識しています:
- 曜日情報を文字列で持たせる(柔軟性重視)
 - UIでのON/OFF状態を
enableで管理 updatedAtで最近更新された習慣を抽出可能に
また、このテーブルが「日常的に触るデータの中心」になるため、シンプルでありつつ拡張しやすい構造を意識しました。
カテゴリマスターとサブカテゴリ
カテゴリ構成は、マスター情報とユーザーが追加できるカスタムカテゴリを分けて管理しています。
さらに、統合して扱うためのSubCategoryViewを用意しています。
まとめると以下になります。
- 「アプリが持つ固定カテゴリ」
 - 「ユーザーが作成したカテゴリ」
 - 「それらをまとめてUIで扱うビュー」
 
これにより、アプリ側で定義されたカテゴリ構成を崩さずに、追加、編集行うことができます。
// カテゴリマスター
@Entity(tableName = "category_master")
data class CategoryMaster(
    @PrimaryKey val id: Long,
    val name: String,
    val iconName: String,
)
// サブカテゴリマスター
@Entity(tableName = "sub_categories")
data class SubCategory(
    @PrimaryKey(autoGenerate = true)
    val subCategoryId: Int = 0,
    val userId: String,
    val categoryMasterId: Int,
    val name: String,
    val createdAt: Long,
    val updatedAt: Long
)
// サブカテゴリビュー
@DatabaseView("SELECT ... UNION ALL SELECT ...", viewName = "sub_category_view")
data class SubCategoryView(...)
テーブルの考え方
CategoryMaster:アプリにあらかじめ定義された固定マスター(ユーザー操作では変更しない前提のデータ)SubCategoryMaster:アプリにあらかじめ定義されたサブカテゴリ固定マスター(ユーザー操作では変更しない前提のデータ)SubCategoryView: マスター側のサブカテゴリ(固定定義)とユーザーが追加したサブカテゴリの両方をまとめた中間ビューです。
ポイント
- マスターは固定データ(アプリ側で定義)
 - サブカテゴリはユーザー追加可能
 Viewで両者を統合し、UIやRepository層から一元的に扱えるようにした
Repository層の設計
UI側では単純に getAllSubCategories() を呼ぶだけで両方が取れるようになるためシンプルです。
class SubCategoryRepository @Inject constructor(
    private val subCategoryDao: SubCategoryDao
) {
    fun getAllSubCategories(userId: String): Flow<List<SubCategoryView>> =
        subCategoryDao.getSubCategoriesByUser(userId)
}
これにより、ViewModel からはカテゴリ構造を意識せずに済みます。
Repository層での結合例
Roomではテーブル間結合をDAOでも書けますが、アプリ内のビジネスロジックを考慮し、Repositoryでまとめています。
class SubCategoryRepository @Inject constructor(
    private val subCategoryDao: SubCategoryDao
) {
    fun getAllCategoryMasters(): List<CategoryMaster> =
        subCategoryDao.getAllCategoryMasters()
    fun getAllSubCategories(userId: String): Flow<List<SubCategoryView>> =
        subCategoryDao.getSubCategoriesByUser(userId)
}
DAOは「DBアクセス専用」と割り切り、Repositoryで「カテゴリマスターとユーザー定義サブカテゴリを統合する」などのアプリ独自の集約処理を行っています。
これにより、ViewModel 側では「構造化済みのカテゴリデータ」をシンプルに扱えます。
Flowでリアクティブに更新検知
Roomのクエリに Flow を使うと、DBの変更をUIに自動で反映できます。
@Dao
interface HabitDao {
    @Query("SELECT * FROM habit ORDER BY updatedAt DESC")
    fun getAllHabits(): Flow<List<Habit>>
    @Update
    suspend fun update(habit: Habit)
    @Delete
    suspend fun delete(habit: Habit)
}
また、ViewModelではcollectAsState()と組み合わせることで、「DB更新 → ViewModel → UI再描画」のリアクティブな流れが完成します。
そして、今後は以下のような機能(仮)を予定しています。
| 機能 | 対応予定テーブル | 
|---|---|
| 習慣達成ログ(1日ごと) | habit_log | 
| 統計グラフ(週・月単位) | habit_log + 集計処理 | 
| 継続率表示 | habit_log | 
そのため、Habit テーブル自体には「現在の状態」だけを保持し、履歴や集計は別テーブルで管理します。
今後は「習慣の達成ログ」や「統計グラフ」も実装予定です。現在の状態はHabit テーブルに持たせ、履歴は別テーブルに分離しました。
// ログテーブル
@Entity(tableName = "habit_log")
data class HabitLog(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val habitId: Long,
    val date: String,
    val isCompleted: Boolean
)
分離のメリット
- ログ機能を追加しても 
Habitテーブルを壊さない - 日単位・週単位の集計も簡単に行える
 - 「状態の履歴」と「定義情報」を独立して扱える
 
このように分離しておくことで、ログ機能を追加しても既存テーブルを壊さずに拡張できます。
まとめ
| 観点 | 方針 | 
|---|---|
| Entity設計 | 責務を分け、拡張性を確保 | 
| Repository構成 | ビジネスロジックを一元管理 | 
| Flow活用 | UI更新をリアクティブ化 | 
| 将来の拡張 | ログ・統計などを別テーブルで実装可能に | 
次回は、このデータ構造を使って Room × Flow × ViewModel の連携実装 を進めます。
「DB更新 → ViewModel → Compose再描画」までのリアクティブな流れを、実際のコードとUIを交えて執筆する予定です。

  
  
  
  