What You Will Build
A recipe browsing screen with icon-based category cards, 5-star ratings, and a scrollable list. Each card uses a rounded container icon, title, subtitle, and a row of star icons. This is the core list pattern for recipe, book, and media apps.
Why This Pattern Matters
List screens with rich cards are the most common screen type in content apps. This exercise covers LazyColumn with spacedBy arrangement, Box with clip and background for icon containers, and reusable Row layouts for metadata display.
Step 1: Define the Recipe Model
data class Recipe(
val title: String,
val subtitle: String,
val icon: ImageVector
)
val recipes = listOf(
Recipe("Pasta Carbonara", "Classic Italian comfort", Icons.Default.Restaurant),
Recipe("Sushi Rolls", "Fresh Japanese cuisine", Icons.Default.SetMeal),
Recipe("Caesar Salad", "Crispy and refreshing", Icons.Default.Spa),
Recipe("Grilled Steak", "Perfectly seasoned", Icons.Default.LocalDining),
Recipe("Banana Bread", "Sweet and moist", Icons.Default.Cake),
Recipe("Smoothie Bowl", "Healthy breakfast option", Icons.Default.Blender)
)
Step 2: Build the Recipe Card
@Composable
fun RecipeCard(recipe: Recipe) {
Card(Modifier.fillMaxWidth()) {
Row(Modifier.padding(16.dp)) {
Box(
Modifier.size(80.dp)
.clip(RoundedCornerShape(12.dp))
.background(MaterialTheme.colorScheme.primaryContainer),
contentAlignment = Alignment.Center
) {
Icon(recipe.icon, null, modifier = Modifier.size(36.dp))
}
Spacer(Modifier.width(16.dp))
Column(Modifier.weight(1f)) {
Text(recipe.title, fontWeight = FontWeight.Bold, fontSize = 16.sp)
Text(
recipe.subtitle,
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 14.sp
)
Spacer(Modifier.height(8.dp))
Row {
repeat(5) {
Icon(
Icons.Default.Star, null,
modifier = Modifier.size(16.dp),
tint = Color(0xFFFFD700)
)
}
}
}
}
}
}
Step 3: Assemble the List
Scaffold(topBar = { TopAppBar(title = { Text("Recipes") }) }) { padding ->
LazyColumn(
modifier = Modifier.padding(padding).padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(recipes) { recipe -> RecipeCard(recipe) }
}
}
Tips and Pitfalls
- MaterialTheme.colorScheme.primaryContainer automatically adapts the icon background to light/dark themes.
- Arrangement.spacedBy(12.dp) is cleaner than adding Spacers between items manually.
- Weight(1f) on the Column ensures the text section fills remaining space after the fixed-size icon box.
- For real apps: Replace ImageVector icons with AsyncImage from Coil to load recipe photos from a backend.
Min SDK: 21 | Compose BOM: 2024.01.00+