【習慣トラッカー開発記 #6】直感的に使える「今日の習慣一覧画面」を実装する

はじめに

前回の記事では、Room × Flow × ViewModelでリアクティブなデータ更新を実装を紹介しました。

そして、今回は実際の画面についてです。

まず始めになぜ「ホーム画面」ではなく、最初に「今日の習慣一覧画面」から作り始めたのか?

その理由を説明します。結論として、今回作成するアプリのホーム画面は分析機能などの情報量が多く、アプリの核となるデータ構造が固まってから着手する方が開発効率が良いと判断しました。

なぜなら、ホーム画面で想定している画面構成として、登録している習慣を分析して円グラフ化したり、習慣をいつ達成して何日連続で達成できているのかなどアプリの土台が出来上がった後の補足的な画面だからです。

そのため、比較的本アプリの顔でもありユーザーが毎日確認する今日の習慣一覧画面を作成した過程を紹介します。

そのため、まずはアプリの基本操作となる「今日やる習慣」を確認する画面から先に作る流れにしました。

今回は、この今日の習慣一覧画面をどのように作ったかを、UI構成・ViewModel連携・Composeならではのポイントを交えて紹介します。

今日の習慣一覧画面の役割

習慣アプリでは、ユーザーが「今日やるべき習慣」を一目で確認できることが重要です。

この画面では、各習慣のタイトル、カテゴリアイコン、チェックボタンを表示し、進捗を簡単に管理できるUIを目指しました。

そのため、この画面に求められる役割は以下になります。

  • 今日の習慣を一覧で確認できる
  • カテゴリアイコン+タイトルで視認性を確保
  • ワンタップで達成チェックができる
  • タップで習慣の編集画面に遷移
  • FAB から新規習慣を登録できる

「毎日絶対開かれる画面」なので、操作を迷わせないシンプルなUIを目指しました。

UI構成とレイアウト

Jetpack Composeを使い、画面全体をScaffoldで構成しました。

上部に AppBar、右下に FloatingActionButton を配置しています。

■UIのポイント

各習慣は Card としてリスト表示し、以下を並べています。

  • カテゴリアイコン
  • タイトル
  • 達成チェックアイコン(ON/OFF切り替え)

チェックボタンは ON のときはカラーアイコン、OFF のときはアウトラインアイコンを表示し、状態が直感的にわかるようにしました。

■ カードタップで編集画面に遷移

カードは clickable を付けておき、タップすると編集画面に遷移できます。

チェックと編集の操作が干渉しないよう、右側に IconButton を配置することで誤タップも防いでいます。

Card(
    modifier = Modifier
        .fillMaxWidth()
        .clickable { onClick() }, // カードタップで編集画面に遷移
    elevation = CardDefaults.cardElevation(4.dp),
    colors = CardDefaults.cardColors(
        containerColor = MaterialTheme.colorScheme.surfaceVariant
    )
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        // 左側:アイコン+タイトル(編集タップの対象)
        Row(verticalAlignment = Alignment.CenterVertically) {
            Image(
                painter = painterResource(id = habit.iconResId),
                contentDescription = "カテゴリアイコン",
                modifier = Modifier.size(48.dp)
            )
            Spacer(modifier = Modifier.width(8.dp))
            Column {
                Text(
                    text = habit.title,
                    style = MaterialTheme.typography.bodyLarge
                )
            }
        }

        // 右側:チェックアイコン(チェック操作専用)
        IconButton(onClick = {
            onCheckedChange(!habit.isChecked) // チェック状態を反転して渡す
        }) {
            Icon(
                imageVector = if (habit.isChecked)
                    Icons.Default.CheckCircle
                else
                    Icons.Outlined.RadioButtonUnchecked,
                contentDescription = "習慣の達成切り替え",
                tint = if (habit.isChecked) PrimaryColor
                else MaterialTheme.colorScheme.onSurfaceVariant,
            )
        }
    }
}

■ スワイプで削除

SwipeToDeleteHabitCard を導入し、iOSやAndroid でよく見られる「スワイプして削除」の操作に対応しています。

key() を付けているのは、削除後に背景が残る問題を回避するための対策です。

key(habit.id) {
    SwipeToDeleteHabitCard(
        habit = habit,
        ...
    )
}

ViewModelとの連携

今日表示する習慣データは ViewModel から Flow で受け取ります。

val habits by viewModel.habits.collectAsState()

習慣チェックは以下のように ViewModel 経由で更新。

viewModel.completeHabit(habit.id, isChecked, currentTime)

DB → Repository → ViewModel → UIという単方向のデータフローを保ちつつ、Compose の再描画で最新状態をUIに即反映できるように意識しました。

プレビューとモックデータ

この記事で紹介しているコードでは、

@Preview にモックの ViewModel を渡してUI確認できるようにしています。

override val habits = MutableStateFlow(
    listOf(
        HabitUiModel(...),
        HabitUiModel(...),
        HabitUiModel(...)
    )
)

今回のケースのように登録画面DBデータ、Repositoryを用意しなくても簡易的に画面の見た目を固められるため、プレビュー機能は便利です。

まとめ

今回の今日の習慣一覧画面の実装では、以下の実装を紹介しました。

  • Card を使ったシンプルで見やすいレイアウト
  • swipe削除・チェックON/OFF・編集遷移などの操作性
  • ViewModel × Flow × Compose のリアクティブなデータ同期
  • モックデータによるプレビュー確認

次回は、この画面から派生する「習慣登録画面」や「編集画面」の実装について掘り下げていく予定です!

タイトルとURLをコピーしました