How to Make a Social Network App
How to Vibe Code a Native Android Social Network App
Stay in your creative flow. Direct AI models with high-level community intent, stitch together a Jetpack Compose social feed with Story rings, post creation, optimistic likes, and comment threads, and ship a working APK on your phone in under 30 minutes.
Core Stack
Kotlin Compose
Feed Engine
LazyColumn + State
AI Partner
Claude / Gemini
Target Time
25–35 Mins
The Vibe Coding Mobile Philosophy
Traditional social app development gets bogged down in multi-view-type RecyclerViews, nested comment adapters, and complex auth screens before you ever feel the product's vibe. Vibe coding puts the community experience first: you describe the feed rhythm, interaction mechanics, and creator profiles, while conversational AI builds the reactive Compose state graph.
Old-School Mobile Stress
- Writing 400 lines of RecyclerView DiffUtil and ViewHolder boilerplate.
- Like counts desynchronizing between the feed item and profile view.
- Wrestling nested scroll conflicts between horizontal Stories and vertical feeds.
- Stalling on backend schemas before testing if the app feels fun to use.
The Vibe Coder Way
- Prompt AI for a unified
LazyColumnwith a gradient Story bar. - Get instant optimistic UI updates for likes, bookmarks, and comments.
- Filter seamlessly by community hashtag topics and saved bookmarks.
- Plug in Room SQLite or Firebase Firestore later with a single follow-up prompt.
The 4-Step Blueprint
From a blank Android Studio project to a tactile social feed on your handset.
Empty Compose Setup
Create a fresh Empty Activity (Compose) project in Android Studio named VibeSocial. Material 3 provides avatars, badges, chips, and modals out of the box.
Design Your Community
Pick your social aesthetic, storage strategy, and engagement mechanics in the configurator below, then paste the prompt into Claude or Gemini.
SocialPost data class and update likes/replies via .copy() so Compose recomposes reliably.
Single-File Drop-In
Paste the generated code into MainActivity.kt to test posting, liking, bookmarking, topic filtering, and reply threads right on your device.
key = { it.id } to your feed items() block so new posts animate smoothly at the top of the timeline.
Add DMs & Polls
Use the Prompt Vault below to expand into double-tap heart animations, creator profile tabs, direct messaging, and interactive polls.
Vibe Prompt Configurator
Select your community vibe, data persistence, and engagement features. Copy the generated prompt directly into Claude, Cursor, or Gemini.
Essential Elements of a Social Network App
A thriving social network revolves around identity, expression, and connection. Make sure your app covers these 6 core building blocks:
-
Frictionless User Onboarding & Identity
Let users claim a handle, pick their favorite interest tags, and jump straight into a personalized community feed without tedious multi-page forms.
-
Expressive Creator Profiles
Showcase user avatars, bio descriptions, follower/following counts, and tabbed lists of authored posts and saved bookmarks.
-
Interactive News Feed & Stories
Pair a horizontal Story ring carousel with a responsive vertical timeline supporting 1-tap likes, bookmarks, and rich post creation.
-
Threaded Replies & Direct Messaging
Spark genuine conversations with expandable reply modals on every post alongside private 1-on-1 direct messaging threads.
-
Topic Discovery & Hashtag Filtering
Allow members to filter the timeline by topic hashtags (
#AndroidDev,#Design,#VibeCoding) or search by creator handle. -
Notifications & Privacy Controls
Keep users updated on new likes, replies, and mentions while providing clear controls over post visibility and muted topics.
The "Golden" Single-File Compose Template
Replace the contents of your MainActivity.kt with this to run a working Social Network app right now.
package com.example.vibesocial
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
data class SocialPost(
val id: Int,
val authorName: String,
val handle: String,
val topic: String,
val timeAgo: String,
val content: String,
val likes: Int,
val isLiked: Boolean = false,
val isBookmarked: Boolean = false,
val comments: List<String> = emptyList()
)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme(
colorScheme = darkColorScheme(
primary = Color(0xFF8B5CF6),
background = Color(0xFF090D16),
surface = Color(0xFF131B2E)
)
) {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
SocialNetworkScreen()
}
}
}
}
}
@Composable
fun SocialNetworkScreen() {
val stories = listOf("You +", "maya.kt", "alex_ui", "dev.kai", "sara_ai", "leo.gfx")
val topics = listOf("All", "#VibeCoding", "#AndroidDev", "#Design", "#Saved")
var selectedTopic by remember { mutableStateOf("All") }
var draftPost by remember { mutableStateOf("") }
var draftTopic by remember { mutableStateOf("#VibeCoding") }
var activeCommentPostId by remember { mutableStateOf<Int?>(null) }
var newCommentText by remember { mutableStateOf("") }
val posts = remember {
mutableStateListOf(
SocialPost(
id = 1,
authorName = "Maya Lin",
handle = "@maya.kt",
topic = "#VibeCoding",
timeAgo = "4m",
content = "Just shipped a full Compose canvas studio in 22 minutes by prompting Claude for immutable path state stacks. Zero XML!",
likes = 48,
isLiked = true,
comments = listOf("alex_ui: That 60fps pointerInput flow is so clean!", "dev.kai: Drop the prompt!")
),
SocialPost(
id = 2,
authorName = "Alex Rivera",
handle = "@alex_ui",
topic = "#Design",
timeAgo = "19m",
content = "Dark mode slate surfaces (#131B2E) paired with electric violet accents make mobile feeds feel effortless on OLED screens.",
likes = 31,
comments = listOf("sara_ai: Totally agree, way easier on the eyes.")
),
SocialPost(
id = 3,
authorName = "Kai Tanaka",
handle = "@dev.kai",
topic = "#AndroidDev",
timeAgo = "1h",
content = "Hot take: Starting every prototype in a single MainActivity.kt file lets you validate the user experience 5x faster.",
likes = 92,
isBookmarked = true
)
)
}
val visiblePosts = posts.filter { post ->
when (selectedTopic) {
"All" -> true
"#Saved" -> post.isBookmarked
else -> post.topic == selectedTopic
}
}
val storyBrush = Brush.linearGradient(
colors = listOf(Color(0xFFEC4899), Color(0xFF8B5CF6), Color(0xFF06B6D4))
)
Column(modifier = Modifier.fillMaxSize().padding(14.dp)) {
// Top Brand Bar
Row(
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text("VibeSphere", fontSize = 22.sp, fontWeight = FontWeight.ExtraBold, color = Color.White)
Text("Creator Community • ${posts.size} Live Posts", fontSize = 12.sp, color = Color(0xFF94A3B8))
}
Surface(color = Color(0xFF1E293B), shape = RoundedCornerShape(20.dp)) {
Text(
text = "@you_builder",
fontSize = 12.sp,
color = Color(0xFF38BDF8),
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
)
}
}
// Story Rings Carousel
LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.padding(bottom = 12.dp)) {
items(stories) { username ->
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(
modifier = Modifier
.size(54.dp)
.border(width = 2.dp, brush = storyBrush, shape = CircleShape)
.padding(4.dp)
.clip(CircleShape)
.background(Color(0xFF1E293B)),
contentAlignment = Alignment.Center
) {
Text(
text = username.take(2).uppercase(),
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 14.sp
)
}
Spacer(modifier = Modifier.height(4.dp))
Text(username, fontSize = 11.sp, color = Color(0xFFCBD5E1))
}
}
}
// Quick Post Composer Card
Card(
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp)
) {
Column(modifier = Modifier.padding(12.dp)) {
OutlinedTextField(
value = draftPost,
onValueChange = { draftPost = it },
placeholder = { Text("Share what you're vibe coding today...") },
modifier = Modifier.fillMaxWidth(),
maxLines = 3
)
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
listOf("#VibeCoding", "#AndroidDev", "#Design").forEach { tag ->
FilterChip(
selected = draftTopic == tag,
onClick = { draftTopic = tag },
label = { Text(tag, fontSize = 11.sp) }
)
}
}
Button(
enabled = draftPost.isNotBlank(),
onClick = {
posts.add(
index = 0,
element = SocialPost(
id = (posts.maxOfOrNull { it.id } ?: 0) + 1,
authorName = "You",
handle = "@you_builder",
topic = draftTopic,
timeAgo = "Just now",
content = draftPost.trim(),
likes = 1,
isLiked = true
)
)
draftPost = ""
},
contentPadding = PaddingValues(horizontal = 14.dp, vertical = 8.dp)
) {
Text("Post", fontSize = 12.sp, fontWeight = FontWeight.Bold)
}
}
}
}
// Topic Filter Strip
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(bottom = 10.dp)) {
items(topics) { topic ->
FilterChip(
selected = selectedTopic == topic,
onClick = { selectedTopic = topic },
label = { Text(topic, fontSize = 12.sp) }
)
}
}
// Social Feed Timeline
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.weight(1f)) {
items(visiblePosts, key = { it.id }) { post ->
Card(
shape = RoundedCornerShape(18.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
modifier = Modifier.fillMaxWidth()
) {
Column(modifier = Modifier.padding(14.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
Box(
modifier = Modifier.size(38.dp).clip(CircleShape).background(Color(0xFF312E81)),
contentAlignment = Alignment.Center
) {
Text(post.authorName.take(1), color = Color.White, fontWeight = FontWeight.Bold)
}
Column {
Text(post.authorName, fontWeight = FontWeight.Bold, color = Color.White, fontSize = 14.sp)
Text("${post.handle} • ${post.timeAgo}", fontSize = 11.sp, color = Color(0xFF94A3B8))
}
}
Surface(color = Color(0xFF1E293B), shape = RoundedCornerShape(8.dp)) {
Text(post.topic, fontSize = 11.sp, color = Color(0xFF38BDF8), modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp))
}
}
Spacer(modifier = Modifier.height(10.dp))
Text(post.content, color = Color(0xFFE2E8F0), fontSize = 14.sp, lineHeight = 20.sp)
Spacer(modifier = Modifier.height(12.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Text(
text = if (post.isLiked) "♥ ${post.likes}" else "♡ ${post.likes}",
color = if (post.isLiked) Color(0xFFF43F5E) else Color(0xFF94A3B8),
fontWeight = FontWeight.Bold,
fontSize = 13.sp,
modifier = Modifier.clickable {
val idx = posts.indexOfFirst { it.id == post.id }
if (idx != -1) {
val current = posts[idx]
val nextLiked = !current.isLiked
posts[idx] = current.copy(
isLiked = nextLiked,
likes = if (nextLiked) current.likes + 1 else current.likes - 1
)
}
}
)
Text(
text = "💬 ${post.comments.size} Replies",
color = Color(0xFF94A3B8),
fontSize = 13.sp,
modifier = Modifier.clickable { activeCommentPostId = post.id }
)
}
Text(
text = if (post.isBookmarked) "★ Saved" else "☆ Save",
color = if (post.isBookmarked) Color(0xFFFBBF24) else Color(0xFF94A3B8),
fontWeight = FontWeight.SemiBold,
fontSize = 13.sp,
modifier = Modifier.clickable {
val idx = posts.indexOfFirst { it.id == post.id }
if (idx != -1) {
posts[idx] = posts[idx].copy(isBookmarked = !posts[idx].isBookmarked)
}
}
)
}
}
}
}
}
}
activeCommentPostId?.let { targetId ->
val postIndex = posts.indexOfFirst { it.id == targetId }
if (postIndex != -1) {
val targetPost = posts[postIndex]
AlertDialog(
onDismissRequest = { activeCommentPostId = null },
title = { Text("Replies to ${targetPost.handle}", fontSize = 16.sp, fontWeight = FontWeight.Bold) },
text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
if (targetPost.comments.isEmpty()) {
Text("No replies yet. Start the conversation!", fontSize = 13.sp, color = Color(0xFF94A3B8))
} else {
targetPost.comments.forEach { reply ->
Surface(color = Color(0xFF1E293B), shape = RoundedCornerShape(10.dp), modifier = Modifier.fillMaxWidth()) {
Text(reply, fontSize = 13.sp, color = Color.White, modifier = Modifier.padding(10.dp))
}
}
}
OutlinedTextField(
value = newCommentText,
onValueChange = { newCommentText = it },
placeholder = { Text("Write a reply...") },
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
}
},
confirmButton = {
Button(
enabled = newCommentText.isNotBlank(),
onClick = {
posts[postIndex] = targetPost.copy(
comments = targetPost.comments + "you_builder: ${newCommentText.trim()}"
)
newCommentText = ""
}
) {
Text("Reply")
}
},
dismissButton = {
TextButton(onClick = { activeCommentPostId = null }) {
Text("Close")
}
}
)
}
}
}
How to Debug Social Feed Glitches
Keep like counters and nested story rows responsive with these three Compose patterns:
Like Counter Doesn't Redraw
Never mutate a var inside a data class in Compose. Keep properties val and replace the list item via .copy():
posts[idx] = posts[idx].copy(isLiked = !current.isLiked, likes = nextCount)
Feed Jumps When Posting
Always pass a stable unique key into your LazyColumn items block so scroll position stays anchored:
items(visiblePosts, key = { it.id }) { post -> ... }
Nested Scroll Crash
Avoid placing a vertical LazyColumn inside a vertical verticalScroll modifier. Place header cards as item { } blocks inside the main LazyColumn.
The Vibe Prompt Vault
Copy-paste prompts to add double-tap heart bursts, creator profile tabs, and interactive community polls.
💖 Prompt: Add Animated Double-Tap Heart Overlay on Post Cards
"Enhance each post card in SocialNetworkScreen with pointerInput { detectTapGestures(onDoubleTap = { ... }) }. When double-tapped, mark the post as liked and trigger a spring-animated scale and alpha heart icon overlay in the center of the card that fades out after 650ms."
👤 Prompt: Add Bottom Navigation with Creator Profile & DM Inbox
"Wrap SocialNetworkScreen in a Material 3 Scaffold with a NavigationBar containing 3 tabs: Feed, Messages, and Profile. In Messages, show a list of creator conversations that open an interactive chat bubble view. In Profile, show @you_builder's bio, follower stats, and authored posts."
📊 Prompt: Add Interactive Community Polls to Post Composer
"Update SocialPost to optionally hold a list of PollOption(label: String, votes: Int). Add a 'Poll' toggle in the post composer, and render interactive voting bars on poll posts that display percentage progress bars and total vote count once tapped."