What You Will Build

A fitness dashboard with a daily progress overview card, plus a list of tracked habits (Meditation, Exercise, Reading, Water, Sleep) each showing a circular progress indicator and streak count. The layout uses Material 3 Cards, LinearProgressIndicator, and CircularProgressIndicator.

Why This Pattern Matters

Health and habit tracking apps are among the most popular on the Play Store. This project teaches you how to use built-in progress indicators effectively, combine them with data-driven lists, and create a cohesive dashboard layout.

Step 1: Define the Habit Model

data class Habit(
    val name: String,
    val icon: ImageVector,
    val progress: Float,  // 0.0 to 1.0
    val streak: Int       // days
)

val habits = listOf(
    Habit("Meditation", Icons.Default.SelfImprovement, 0.8f, 12),
    Habit("Exercise", Icons.Default.FitnessCenter, 0.6f, 5),
    Habit("Reading", Icons.Default.Book, 0.9f, 20),
    Habit("Water", Icons.Default.WaterDrop, 0.4f, 3),
    Habit("Sleep 8h", Icons.Default.Bedtime, 0.7f, 8)
)

Step 2: Daily Progress Overview Card

Card(
    Modifier.fillMaxWidth(),
    colors = CardDefaults.cardColors(
        containerColor = MaterialTheme.colorScheme.primaryContainer
    )
) {
    Column(Modifier.padding(20.dp)) {
        Text("Today's Progress", fontWeight = FontWeight.Bold)
        Spacer(Modifier.height(8.dp))
        LinearProgressIndicator(
            progress = { 0.7f },
            modifier = Modifier.fillMaxWidth().height(8.dp)
                .clip(RoundedCornerShape(4.dp))
        )
        Spacer(Modifier.height(4.dp))
        Text("70% completed",
             color = MaterialTheme.colorScheme.onSurfaceVariant)
    }
}

Step 3: Habit Cards with Circular Progress

habits.forEach { habit ->
    Card(Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
        Row(Modifier.padding(16.dp),
            verticalAlignment = Alignment.CenterVertically) {
            Icon(habit.icon, null,
                 tint = MaterialTheme.colorScheme.primary)
            Spacer(Modifier.width(12.dp))
            Column(Modifier.weight(1f)) {
                Text(habit.name,
                     fontWeight = FontWeight.SemiBold)
                Text("${habit.streak} day streak",
                     fontSize = 12.sp,
                     color = MaterialTheme.colorScheme
                         .onSurfaceVariant)
            }
            Box(contentAlignment = Alignment.Center) {
                CircularProgressIndicator(
                    progress = { habit.progress },
                    modifier = Modifier.size(40.dp),
                    strokeWidth = 4.dp
                )
                Text("${(habit.progress * 100).toInt()}%",
                     fontSize = 10.sp)
            }
        }
    }
}

Tips and Pitfalls

  • CircularProgressIndicator with centered text requires a Box(contentAlignment = Center) wrapper. The indicator and text stack naturally.
  • Clip the LinearProgressIndicator with RoundedCornerShape(4.dp) for rounded ends. Without clipping, it has square ends.
  • Streak gamification: Add color coding (green for 7+ days, yellow for 3-6, red for <3) to motivate users.
  • Animation: Wrap progress values in animateFloatAsState so they animate from 0 to their target on screen entry.

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.