What You Will Build

A vertically scrolling list of story cards, each featuring an icon thumbnail, title, subtitle, and a star rating row. This pattern is used in book apps, podcast directories, course catalogs, and any content library with ratings.

Why This Pattern Matters

Card-based lists with thumbnails and metadata are the most common UI pattern in Android apps. Getting the Row-inside-Card layout right with proper spacing, alignment, and weight distribution is foundational for building any content browsing experience.

Key Concepts

  • LazyColumn with Arrangement.spacedBy for consistent card spacing.
  • Row inside Card with thumbnail, text column, and weighted layout.
  • repeat() for star ratings generating identical icons efficiently.

The Stories Screen

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StoriesScreen() {
    data class Item(
        val title: String,
        val subtitle: String,
        val icon: ImageVector
    )
    val items = listOf(
        Item("The Art of Code", "A journey through software", Icons.Default.Book),
        Item("Design Patterns", "Building better apps", Icons.Default.DesignServices),
        Item("Mobile First", "Modern development", Icons.Default.PhoneAndroid),
        Item("AI Revolution", "The future is here", Icons.Default.SmartToy),
        Item("Clean Architecture", "Principles and practices", Icons.Default.Architecture),
        Item("Data Stories", "Visualizing insights", Icons.Default.BarChart)
    )

    Scaffold(
        topBar = { TopAppBar(title = { Text("Stories") }) }
    ) { padding ->
        LazyColumn(
            modifier = Modifier.padding(padding).padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            items(items) { item ->
                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(item.icon, null,
                                modifier = Modifier.size(36.dp))
                        }
                        Spacer(Modifier.width(16.dp))
                        Column(Modifier.weight(1f)) {
                            Text(item.title,
                                fontWeight = FontWeight.Bold,
                                fontSize = 16.sp)
                            Text(item.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))
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Tips and Pitfalls

  • Modifier.weight(1f) on the text Column ensures it fills the remaining space after the fixed-size thumbnail.
  • RoundedCornerShape(12.dp) on the thumbnail box creates rounded thumbnails without needing images.
  • Star ratings: Use repeat(5) with conditional tint colors for partial ratings (filled vs outlined stars).
  • Click handling: Add Modifier.clickable { } on the Card for navigation to a detail screen.

Minimum SDK: API 21+ with Compose BOM

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.