What You Will Build
A social media feed with user avatars, post content, like counts, and interaction buttons, modeled after the Pinterest card-based layout. This pattern applies to any content discovery app: news readers, recipe feeds, or photo-sharing platforms.
Why This Pattern Matters
Social feeds are the backbone of content apps. Getting the card structure right with proper avatar rendering, text hierarchy, and action rows teaches you the core layout patterns used in nearly every production Android app.
Key Jetpack Compose Concepts
- LazyColumn with items() for efficient scrollable lists.
- Row + CircleShape clip for user avatar rendering.
- Data classes inside composables for local demo data.
Step 1: Define the Post Data Model
data class Post(
val user: String,
val content: String,
val likes: Int
)
val posts = listOf(
Post("Alice", "Beautiful sunset today! The sky was painted in orange and pink.", 142),
Post("Bob", "Just finished a great workout at the gym. Feeling energized!", 89),
Post("Carol", "New recipe tried today - homemade pasta from scratch.", 234),
Post("Dave", "Weekend road trip plans coming together. Any suggestions?", 67)
)
Step 2: Build the Feed Screen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PinterestScreen() {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Pinterest") },
actions = {
IconButton(onClick = {}) {
Icon(Icons.Default.Notifications, "Notifications")
}
}
)
}
) { padding ->
LazyColumn(Modifier.padding(padding)) {
items(posts) { post ->
Column(Modifier.padding(16.dp)) {
// User row with avatar
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
Modifier.size(40.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.primaryContainer),
contentAlignment = Alignment.Center
) {
Text(post.user.first().toString(),
fontWeight = FontWeight.Bold)
}
Spacer(Modifier.width(12.dp))
Column {
Text(post.user, fontWeight = FontWeight.Bold)
Text("2h ago", fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant)
}
}
Spacer(Modifier.height(8.dp))
Text(post.content)
Spacer(Modifier.height(8.dp))
// Action row
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.FavoriteBorder, "Like",
modifier = Modifier.size(20.dp))
Spacer(Modifier.width(4.dp))
Text("${'$'}{post.likes}")
}
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.ChatBubbleOutline, "Comment",
modifier = Modifier.size(20.dp))
Spacer(Modifier.width(4.dp))
Text("Reply")
}
Icon(Icons.Default.Share, "Share",
modifier = Modifier.size(20.dp))
}
HorizontalDivider(Modifier.padding(top = 12.dp))
}
}
}
}
}
Tips and Pitfalls
- CircleShape clip: Always clip before background to get proper circular rendering. The order
.clip(CircleShape).background(...)matters. - LazyColumn vs Column: Use LazyColumn for feeds with more than a handful of items. It only composes visible items, saving memory.
- HorizontalDivider: This is the Material 3 replacement for the deprecated
Dividercomposable. - Image loading: Replace the avatar Box with
AsyncImagefrom Coil for real profile photos.
Minimum SDK: API 21+ with Compose BOM