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
animateFloatAsStateso they animate from 0 to their target on screen entry.