Social Media App Using Firebase: Complete Android Project with Code
complete, production-ready social media app for Android using Firebase — with authentication, posts, likes, comments, and real-time updates.
App Features
| Feature | Implementation |
|---|---|
| User Authentication | Email/Password + Google Sign-In |
| Create Posts | Text + Image upload |
| News Feed | Real-time chronological feed |
| Like Posts | Real-time like counter |
| Comment System | Nested comments with replies |
| User Profiles | Avatar, bio, post history |
| Real-time Updates | Firestore listeners |
| Push Notifications | FCM (optional) |
Project Structure
text
SocialMediaApp/ ├── app/ │ ├── src/main/java/com/example/socialmedia/ │ │ ├── activities/ │ │ │ ├── LoginActivity.kt │ │ │ ├── SignupActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── CreatePostActivity.kt │ │ │ ├── PostDetailActivity.kt │ │ │ └── ProfileActivity.kt │ │ ├── adapters/ │ │ │ ├── PostAdapter.kt │ │ │ └── CommentAdapter.kt │ │ ├── models/ │ │ │ ├── User.kt │ │ │ ├── Post.kt │ │ │ └── Comment.kt │ │ ├── utils/ │ │ │ ├── FirebaseManager.kt │ │ │ └── ImageUploader.kt │ │ └── fragments/ │ │ ├── HomeFragment.kt │ │ └── ProfileFragment.kt │ ├── res/ │ └── build.gradle.kts ├── google-services.json └── build.gradle.kts (project)
Step 1: Firebase Console Setup
1.1 Create Firebase Project
- Go to Firebase Console
- Click “Add project” → Name:
SocialMediaApp - Disable Google Analytics (optional)
- Click “Create project”
1.2 Register Android App
- Click Android icon to add app
- Package name:
com.example.socialmedia - Register app → Download
google-services.json - Place file in
app/directory
1.3 Enable Authentication
- Go to Authentication → Sign-in methods
- Enable: Email/Password + Google
1.4 Create Firestore Database
- Firestore Database → Create database
- Start in test mode (for development)
- Location: Choose closest to you
1.5 Enable Storage
- Storage → Get Started
- Start in test mode
Step 2: Dependencies
Project-level build.gradle.kts
kotlin
// Top-level build file
plugins {
id("com.android.application") version "8.2.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
id("com.google.gms.google-services") version "4.4.0" apply false
id("com.google.dagger.hilt.android") version "2.48" apply false
}
App-level build.gradle.kts
kotlin
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.gms.google-services")
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
}
android {
namespace = "com.example.socialmedia"
compileSdk = 34
defaultConfig {
applicationId = "com.example.socialmedia"
minSdk = 23
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
buildFeatures {
viewBinding = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
// Core Android
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
// Lifecycle
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
// Navigation
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
// Firebase
implementation(platform("com.google.firebase:firebase-bom:32.7.0"))
implementation("com.google.firebase:firebase-auth-ktx")
implementation("com.google.firebase:firebase-firestore-ktx")
implementation("com.google.firebase:firebase-storage-ktx")
implementation("com.google.android.gms:play-services-auth:20.7.0")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
// Image Loading
implementation("com.github.bumptech.glide:glide:4.16.0")
// Circle ImageView
implementation("de.hdodenhof:circleimageview:3.1.0")
// Hilt (Dependency Injection)
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
// Shimmer Effect (Loading)
implementation("com.facebook.shimmer:shimmer:0.5.0")
}
Step 3: Data Models

models/User.kt
kotlin
package com.example.socialmedia.models
data class User(
val uid: String = "",
val name: String = "",
val email: String = "",
val bio: String = "",
val profileImageUrl: String = "",
val followers: List<String> = emptyList(),
val following: List<String> = emptyList(),
val postCount: Int = 0,
val createdAt: Long = System.currentTimeMillis()
)
models/Post.kt
kotlin
package com.example.socialmedia.models
data class Post(
val postId: String = "",
val userId: String = "",
val userName: String = "",
val userProfileImage: String = "",
val content: String = "",
val imageUrl: String = "",
val timestamp: Long = System.currentTimeMillis(),
val likes: MutableList<String> = mutableListOf(),
val likeCount: Int = 0,
val commentCount: Int = 0
)
models/Comment.kt
kotlin
package com.example.socialmedia.models
data class Comment(
val commentId: String = "",
val postId: String = "",
val userId: String = "",
val userName: String = "",
val userProfileImage: String = "",
val text: String = "",
val timestamp: Long = System.currentTimeMillis(),
val likes: MutableList<String> = mutableListOf()
)
Step 4: Firebase Manager
utils/FirebaseManager.kt
kotlin
package com.example.socialmedia.utils
import android.net.Uri
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.GoogleAuthProvider
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.Query
import com.google.firebase.storage.FirebaseStorage
import com.example.socialmedia.models.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.tasks.await
class FirebaseManager {
private val auth = FirebaseAuth.getInstance()
private val db = FirebaseFirestore.getInstance()
private val storage = FirebaseStorage.getInstance().reference
companion object {
const val USERS_COLLECTION = "users"
const val POSTS_COLLECTION = "posts"
const val COMMENTS_COLLECTION = "comments"
}
// ============ AUTHENTICATION ============
val currentUser: User?
get() {
val firebaseUser = auth.currentUser ?: return null
// Fetch from Firestore or return basic
return User(uid = firebaseUser.uid, email = firebaseUser.email ?: "", name = firebaseUser.displayName ?: "")
}
suspend fun loginWithEmail(email: String, password: String): Result<String> {
return try {
val result = auth.signInWithEmailAndPassword(email, password).await()
Result.success(result.user?.uid ?: "")
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun signupWithEmail(name: String, email: String, password: String): Result<User> {
return try {
val result = auth.createUserWithEmailAndPassword(email, password).await()
val uid = result.user?.uid ?: throw Exception("User creation failed")
val user = User(
uid = uid,
name = name,
email = email,
createdAt = System.currentTimeMillis()
)
db.collection(USERS_COLLECTION).document(uid).set(user).await()
Result.success(user)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun googleSignIn(idToken: String): Result<User> {
return try {
val credential = GoogleAuthProvider.getCredential(idToken, null)
val authResult = auth.signInWithCredential(credential).await()
val uid = authResult.user?.uid ?: throw Exception("Google sign in failed")
// Check if user exists
val userDoc = db.collection(USERS_COLLECTION).document(uid).get().await()
val user = if (userDoc.exists()) {
userDoc.toObject(User::class.java)!!
} else {
val newUser = User(
uid = uid,
name = authResult.user?.displayName ?: "",
email = authResult.user?.email ?: "",
profileImageUrl = authResult.user?.photoUrl?.toString() ?: ""
)
db.collection(USERS_COLLECTION).document(uid).set(newUser).await()
newUser
}
Result.success(user)
} catch (e: Exception) {
Result.failure(e)
}
}
fun logout() {
auth.signOut()
}
// ============ POSTS ============
fun getAllPosts(): Flow<List<Post>> = callbackFlow {
val listener = db.collection(POSTS_COLLECTION)
.orderBy("timestamp", Query.Direction.DESCENDING)
.addSnapshotListener { snapshot, error ->
if (error != null) {
close(error)
return@addSnapshotListener
}
val posts = snapshot?.documents?.mapNotNull { doc ->
doc.toObject(Post::class.java)?.copy(postId = doc.id)
} ?: emptyList()
trySend(posts)
}
awaitClose { listener.remove() }
}.flowOn(Dispatchers.IO)
suspend fun createPost(post: Post, imageUri: Uri? = null): Result<String> {
return try {
var imageUrl = ""
if (imageUri != null) {
val fileRef = storage.child("posts/${System.currentTimeMillis()}_${post.userId}.jpg")
val uploadTask = fileRef.putFile(imageUri).await()
imageUrl = fileRef.downloadUrl.await().toString()
}
val finalPost = post.copy(
imageUrl = imageUrl,
timestamp = System.currentTimeMillis()
)
val docRef = db.collection(POSTS_COLLECTION).document()
val postId = docRef.id
docRef.set(finalPost.copy(postId = postId)).await()
// Update user's post count
db.collection(USERS_COLLECTION).document(post.userId)
.update("postCount", com.google.firebase.firestore.FieldValue.increment(1))
.await()
Result.success(postId)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun likePost(postId: String, userId: String): Result<Unit> {
return try {
val postRef = db.collection(POSTS_COLLECTION).document(postId)
db.runTransaction { transaction ->
val snapshot = transaction.get(postRef)
val likes = snapshot.get("likes") as? List<String> ?: emptyList()
val newLikes = if (likes.contains(userId)) {
likes.filter { it != userId }
} else {
likes + userId
}
transaction.update(postRef, "likes", newLikes)
transaction.update(postRef, "likeCount", newLikes.size)
}.await()
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun deletePost(postId: String): Result<Unit> {
return try {
// Delete post
db.collection(POSTS_COLLECTION).document(postId).delete().await()
// Delete all comments for this post
val comments = db.collection(COMMENTS_COLLECTION)
.whereEqualTo("postId", postId)
.get()
.await()
for (comment in comments.documents) {
comment.reference.delete().await()
}
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}
// ============ COMMENTS ============
fun getCommentsForPost(postId: String): Flow<List<Comment>> = callbackFlow {
val listener = db.collection(COMMENTS_COLLECTION)
.whereEqualTo("postId", postId)
.orderBy("timestamp", Query.Direction.ASCENDING)
.addSnapshotListener { snapshot, error ->
if (error != null) {
close(error)
return@addSnapshotListener
}
val comments = snapshot?.documents?.mapNotNull { doc ->
doc.toObject(Comment::class.java)?.copy(commentId = doc.id)
} ?: emptyList()
trySend(comments)
}
awaitClose { listener.remove() }
}.flowOn(Dispatchers.IO)
suspend fun addComment(comment: Comment): Result<String> {
return try {
val docRef = db.collection(COMMENTS_COLLECTION).document()
val commentId = docRef.id
docRef.set(comment.copy(commentId = commentId)).await()
// Update comment count on post
db.collection(POSTS_COLLECTION).document(comment.postId)
.update("commentCount", com.google.firebase.firestore.FieldValue.increment(1))
.await()
Result.success(commentId)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun deleteComment(commentId: String, postId: String): Result<Unit> {
return try {
db.collection(COMMENTS_COLLECTION).document(commentId).delete().await()
// Decrement comment count
db.collection(POSTS_COLLECTION).document(postId)
.update("commentCount", com.google.firebase.firestore.FieldValue.increment(-1))
.await()
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}
// ============ USER PROFILE ============
suspend fun getUserProfile(userId: String): Result<User> {
return try {
val doc = db.collection(USERS_COLLECTION).document(userId).get().await()
val user = doc.toObject(User::class.java)
if (user != null) {
Result.success(user)
} else {
Result.failure(Exception("User not found"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun updateUserProfile(userId: String, updates: Map<String, Any>): Result<Unit> {
return try {
db.collection(USERS_COLLECTION).document(userId).update(updates).await()
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun getUserPosts(userId: String): Flow<List<Post>> = callbackFlow {
val listener = db.collection(POSTS_COLLECTION)
.whereEqualTo("userId", userId)
.orderBy("timestamp", Query.Direction.DESCENDING)
.addSnapshotListener { snapshot, error ->
if (error != null) {
close(error)
return@addSnapshotListener
}
val posts = snapshot?.documents?.mapNotNull { doc ->
doc.toObject(Post::class.java)?.copy(postId = doc.id)
} ?: emptyList()
trySend(posts)
}
awaitClose { listener.remove() }
}.flowOn(Dispatchers.IO)
suspend fun uploadProfileImage(userId: String, imageUri: Uri): Result<String> {
return try {
val fileRef = storage.child("profile_images/$userId.jpg")
fileRef.putFile(imageUri).await()
val imageUrl = fileRef.downloadUrl.await().toString()
db.collection(USERS_COLLECTION).document(userId)
.update("profileImageUrl", imageUrl)
.await()
Result.success(imageUrl)
} catch (e: Exception) {
Result.failure(e)
}
}
}
Step 5: Activities
activities/LoginActivity.kt
kotlin
package com.example.socialmedia.activities
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.example.socialmedia.databinding.ActivityLoginBinding
import com.example.socialmedia.utils.FirebaseManager
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.api.ApiException
import com.google.firebase.auth.GoogleAuthProvider
import kotlinx.coroutines.*
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
private lateinit var firebaseManager: FirebaseManager
private lateinit var googleSignInClient: GoogleSignInClient
private val googleSignInLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
try {
val account = task.getResult(ApiException::class.java)
firebaseAuthWithGoogle(account)
} catch (e: ApiException) {
Toast.makeText(this, "Google sign in failed: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
firebaseManager = FirebaseManager()
// Check if already logged in
if (firebaseManager.currentUser != null) {
goToMainActivity()
return
}
// Configure Google Sign In
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(this, gso)
setupClickListeners()
}
private fun setupClickListeners() {
binding.btnLogin.setOnClickListener {
val email = binding.etEmail.text.toString().trim()
val password = binding.etPassword.text.toString().trim()
if (email.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "Please fill all fields", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
loginWithEmail(email, password)
}
binding.btnGoogleSignIn.setOnClickListener {
signInWithGoogle()
}
binding.tvSignupLink.setOnClickListener {
startActivity(Intent(this, SignupActivity::class.java))
}
}
private fun loginWithEmail(email: String, password: String) {
binding.progressBar.visibility = android.view.View.VISIBLE
binding.btnLogin.isEnabled = false
CoroutineScope(Dispatchers.IO).launch {
val result = firebaseManager.loginWithEmail(email, password)
withContext(Dispatchers.Main) {
binding.progressBar.visibility = android.view.View.GONE
binding.btnLogin.isEnabled = true
if (result.isSuccess) {
Toast.makeText(this@LoginActivity, "Login successful", Toast.LENGTH_SHORT).show()
goToMainActivity()
} else {
Toast.makeText(
this@LoginActivity,
"Login failed: ${result.exceptionOrNull()?.message}",
Toast.LENGTH_SHORT
).show()
}
}
}
}
private fun signInWithGoogle() {
val signInIntent = googleSignInClient.signInIntent
googleSignInLauncher.launch(signInIntent)
}
private fun firebaseAuthWithGoogle(account: GoogleSignInAccount) {
binding.progressBar.visibility = android.view.View.VISIBLE
val credential = GoogleAuthProvider.getCredential(account.idToken, null)
CoroutineScope(Dispatchers.IO).launch {
val result = firebaseManager.googleSignIn(account.idToken ?: "")
withContext(Dispatchers.Main) {
binding.progressBar.visibility = android.view.View.GONE
if (result.isSuccess) {
Toast.makeText(this@LoginActivity, "Google sign in successful", Toast.LENGTH_SHORT).show()
goToMainActivity()
} else {
Toast.makeText(
this@LoginActivity,
"Google sign in failed: ${result.exceptionOrNull()?.message}",
Toast.LENGTH_SHORT
).show()
}
}
}
}
private fun goToMainActivity() {
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
}
}
activities/SignupActivity.kt
kotlin
package com.example.socialmedia.activities
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.socialmedia.databinding.ActivitySignupBinding
import com.example.socialmedia.utils.FirebaseManager
import kotlinx.coroutines.*
class SignupActivity : AppCompatActivity() {
private lateinit var binding: ActivitySignupBinding
private lateinit var firebaseManager: FirebaseManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySignupBinding.inflate(layoutInflater)
setContentView(binding.root)
firebaseManager = FirebaseManager()
binding.btnSignup.setOnClickListener {
val name = binding.etName.text.toString().trim()
val email = binding.etEmail.text.toString().trim()
val password = binding.etPassword.text.toString().trim()
val confirmPassword = binding.etConfirmPassword.text.toString().trim()
if (name.isEmpty() || email.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "Please fill all fields", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (password != confirmPassword) {
Toast.makeText(this, "Passwords do not match", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (password.length < 6) {
Toast.makeText(this, "Password must be at least 6 characters", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
signupWithEmail(name, email, password)
}
binding.tvLoginLink.setOnClickListener {
finish()
}
}
private fun signupWithEmail(name: String, email: String, password: String) {
binding.progressBar.visibility = android.view.View.VISIBLE
binding.btnSignup.isEnabled = false
CoroutineScope(Dispatchers.IO).launch {
val result = firebaseManager.signupWithEmail(name, email, password)
withContext(Dispatchers.Main) {
binding.progressBar.visibility = android.view.View.GONE
binding.btnSignup.isEnabled = true
if (result.isSuccess) {
Toast.makeText(this@SignupActivity, "Account created successfully", Toast.LENGTH_SHORT).show()
finish()
} else {
Toast.makeText(
this@SignupActivity,
"Signup failed: ${result.exceptionOrNull()?.message}",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
activities/MainActivity.kt
kotlin
package com.example.socialmedia.activities
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.setupActionBarWithNavController
import com.example.socialmedia.R
import com.example.socialmedia.databinding.ActivityMainBinding
import com.example.socialmedia.utils.FirebaseManager
import com.google.android.material.bottomnavigation.BottomNavigationView
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var firebaseManager: FirebaseManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
firebaseManager = FirebaseManager()
// Set up bottom navigation
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
setupActionBarWithNavController(navController, binding.appBarMain.toolbar)
navView.setupWithNavController(navController)
// Customize toolbar title
supportActionBar?.title = "Social Media"
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_logout -> {
firebaseManager.logout()
val intent = android.content.Intent(this, LoginActivity::class.java)
intent.flags = android.content.Intent.FLAG_ACTIVITY_NEW_TASK or android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
true
}
R.id.action_profile -> {
// Navigate to profile
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
activities/CreatePostActivity.kt
kotlin
package com.example.socialmedia.activities
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.example.socialmedia.databinding.ActivityCreatePostBinding
import com.example.socialmedia.models.Post
import com.example.socialmedia.utils.FirebaseManager
import com.github.dhaval2404.imagepicker.ImagePicker
import kotlinx.coroutines.*
class CreatePostActivity : AppCompatActivity() {
private lateinit var binding: ActivityCreatePostBinding
private lateinit var firebaseManager: FirebaseManager
private var selectedImageUri: Uri? = null
private val pickImageLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
selectedImageUri = result.data?.data
binding.ivSelectedImage.setImageURI(selectedImageUri)
binding.ivSelectedImage.visibility = android.view.View.VISIBLE
binding.btnRemoveImage.visibility = android.view.View.VISIBLE
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCreatePostBinding.inflate(layoutInflater)
setContentView(binding.root)
firebaseManager = FirebaseManager()
binding.btnSelectImage.setOnClickListener {
ImagePicker.with(this)
.cropSquare()
.compress(1024)
.maxResultSize(1080, 1080)
.createIntent { intent ->
pickImageLauncher.launch(intent)
}
}
binding.btnRemoveImage.setOnClickListener {
selectedImageUri = null
binding.ivSelectedImage.setImageBitmap(null)
binding.ivSelectedImage.visibility = android.view.View.GONE
binding.btnRemoveImage.visibility = android.view.View.GONE
}
binding.btnPost.setOnClickListener {
val content = binding.etContent.text.toString().trim()
if (content.isEmpty() && selectedImageUri == null) {
Toast.makeText(this, "Please enter some content or select an image", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
createPost(content)
}
}
private fun createPost(content: String) {
binding.progressBar.visibility = android.view.View.VISIBLE
binding.btnPost.isEnabled = false
val currentUser = firebaseManager.currentUser ?: return
val post = Post(
userId = currentUser.uid,
userName = currentUser.name,
userProfileImage = currentUser.profileImageUrl,
content = content
)
CoroutineScope(Dispatchers.IO).launch {
val result = firebaseManager.createPost(post, selectedImageUri)
withContext(Dispatchers.Main) {
binding.progressBar.visibility = android.view.View.GONE
binding.btnPost.isEnabled = true
if (result.isSuccess) {
Toast.makeText(this@CreatePostActivity, "Post created successfully", Toast.LENGTH_SHORT).show()
finish()
} else {
Toast.makeText(
this@CreatePostActivity,
"Failed to create post: ${result.exceptionOrNull()?.message}",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
Step 6: Adapters
adapters/PostAdapter.kt
kotlin
package com.example.socialmedia.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.socialmedia.databinding.ItemPostBinding
import com.example.socialmedia.models.Post
import com.example.socialmedia.utils.FirebaseManager
import kotlinx.coroutines.*
class PostAdapter(
private val onLikeClick: (String) -> Unit,
private val onCommentClick: (String) -> Unit,
private val onProfileClick: (String) -> Unit
) : ListAdapter<Post, PostAdapter.PostViewHolder>(PostDiffCallback()) {
private lateinit var firebaseManager: FirebaseManager
private val currentUserId = FirebaseManager().currentUser?.uid ?: ""
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
val binding = ItemPostBinding.inflate(LayoutInflater.from(parent.context), parent, false)
firebaseManager = FirebaseManager()
return PostViewHolder(binding)
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
holder.bind(getItem(position))
}
inner class PostViewHolder(private val binding: ItemPostBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(post: Post) {
// User info
binding.tvUserName.text = post.userName
binding.tvTimestamp.text = formatTime(post.timestamp)
Glide.with(binding.root.context)
.load(post.userProfileImage)
.circleCrop()
.into(binding.ivProfileImage)
// Post content
binding.tvContent.text = post.content
if (post.imageUrl.isNotEmpty()) {
binding.ivPostImage.visibility = android.view.View.VISIBLE
Glide.with(binding.root.context)
.load(post.imageUrl)
.into(binding.ivPostImage)
} else {
binding.ivPostImage.visibility = android.view.View.GONE
}
// Like button state
val isLiked = post.likes.contains(currentUserId)
binding.btnLike.setImageResource(
if (isLiked) android.R.drawable.star_on else android.R.drawable.star_off
)
// Stats
binding.tvLikes.text = "${post.likeCount} likes"
binding.tvComments.text = "${post.commentCount} comments"
// Click listeners
binding.btnLike.setOnClickListener {
onLikeClick(post.postId)
// Optimistic update
if (isLiked) {
post.likes.remove(currentUserId)
} else {
post.likes.add(currentUserId)
}
bind(post) // Refresh
CoroutineScope(Dispatchers.IO).launch {
firebaseManager.likePost(post.postId, currentUserId)
}
}
binding.btnComment.setOnClickListener {
onCommentClick(post.postId)
}
binding.ivProfileImage.setOnClickListener {
onProfileClick(post.userId)
}
}
}
private fun formatTime(timestamp: Long): String {
val now = System.currentTimeMillis()
val diff = now - timestamp
return when {
diff < 60000 -> "Just now"
diff < 3600000 -> "${diff / 60000} minutes ago"
diff < 86400000 -> "${diff / 3600000} hours ago"
else -> "${diff / 86400000} days ago"
}
}
class PostDiffCallback : DiffUtil.ItemCallback<Post>() {
override fun areItemsTheSame(oldItem: Post, newItem: Post): Boolean = oldItem.postId == newItem.postId
override fun areContentsTheSame(oldItem: Post, newItem: Post): Boolean = oldItem == newItem
}
}
Step 7: Fragments
fragments/HomeFragment.kt
kotlin
package com.example.socialmedia.fragments
import android.content.Intent
import android.os.Bundle
import android.view.*
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.socialmedia.R
import com.example.socialmedia.activities.CommentActivity
import com.example.socialmedia.activities.CreatePostActivity
import com.example.socialmedia.adapters.PostAdapter
import com.example.socialmedia.databinding.FragmentHomeBinding
import com.example.socialmedia.utils.FirebaseManager
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private lateinit var firebaseManager: FirebaseManager
private lateinit var postAdapter: PostAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
firebaseManager = FirebaseManager()
setupRecyclerView()
setupFab()
loadPosts()
}
private fun setupRecyclerView() {
postAdapter = PostAdapter(
onLikeClick = { postId ->
likePost(postId)
},
onCommentClick = { postId ->
val intent = Intent(requireContext(), CommentActivity::class.java)
intent.putExtra("postId", postId)
startActivity(intent)
},
onProfileClick = { userId ->
// Navigate to profile
}
)
binding.rvPosts.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = postAdapter
}
}
private fun setupFab() {
binding.fabCreatePost.setOnClickListener {
startActivity(Intent(requireContext(), CreatePostActivity::class.java))
}
}
private fun loadPosts() {
binding.shimmerViewContainer.startShimmer()
viewLifecycleOwner.lifecycleScope.launch {
firebaseManager.getAllPosts().collectLatest { posts ->
postAdapter.submitList(posts)
binding.shimmerViewContainer.stopShimmer()
binding.shimmerViewContainer.visibility = android.view.View.GONE
if (posts.isEmpty()) {
binding.tvEmptyState.visibility = android.view.View.VISIBLE
} else {
binding.tvEmptyState.visibility = android.view.View.GONE
}
}
}
}
private fun likePost(postId: String) {
val currentUser = firebaseManager.currentUser ?: return
viewLifecycleOwner.lifecycleScope.launch {
firebaseManager.likePost(postId, currentUser.uid)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Step 8: Layout Files
res/layout/activity_login.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_logo"
android:layout_marginBottom="32dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Social Media App"
android:textSize="28sp"
android:textStyle="bold"
android:layout_marginBottom="32dp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:endIconMode="clear_text">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:endIconMode="password_toggle">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="Login"
android:textAllCaps="false"/>
<Button
android:id="@+id/btnGoogleSignIn"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="12dp"
android:text="Sign in with Google"
android:textAllCaps="false"
style="@style/Widget.Material3.Button.OutlinedButton"/>
<TextView
android:id="@+id/tvSignupLink"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Don't have an account? Sign up"
android:textColor="@color/purple_500"
android:textSize="14sp"/>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"/>
</LinearLayout>
res/layout/item_post.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<!-- User Info Row -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/ivProfileImage"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_default_avatar"
app:civ_border_width="2dp"
app:civ_border_color="@color/purple_500"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="12dp">
<TextView
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="User Name"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tvTimestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2 hours ago"
android:textSize="12sp"
android:textColor="@android:color/darker_gray"/>
</LinearLayout>
</LinearLayout>
<!-- Post Content -->
<TextView
android:id="@+id/tvContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Post content goes here..."
android:textSize="14sp"/>
<!-- Post Image -->
<ImageView
android:id="@+id/ivPostImage"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="12dp"
android:scaleType="centerCrop"
android:visibility="gone"/>
<!-- Action Buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<ImageButton
android:id="@+id/btnLike"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/star_off"
android:contentDescription="Like"/>
<ImageButton
android:id="@+id/btnComment"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_edit"
android:contentDescription="Comment"/>
<ImageButton
android:id="@+id/btnShare"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_share"
android:contentDescription="Share"/>
</LinearLayout>
<!-- Stats -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tvLikes"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="0 likes"
android:textSize="12sp"/>
<TextView
android:id="@+id/tvComments"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:text="0 comments"
android:textSize="12sp"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
res/layout/fragment_home.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvPosts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="80dp"
android:clipToPadding="false"/>
<TextView
android:id="@+id/tvEmptyState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="No posts yet.\nCreate your first post!"
android:textAlignment="center"
android:textSize="16sp"
android:visibility="gone"/>
<com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/shimmerViewContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Shimmer placeholders -->
<View android:layout_width="match_parent" android:layout_height="100dp" android:background="@android:color/darker_gray" android:layout_margin="8dp"/>
<View android:layout_width="match_parent" android:layout_height="100dp" android:background="@android:color/darker_gray" android:layout_margin="8dp"/>
<View android:layout_width="match_parent" android:layout_height="100dp" android:background="@android:color/darker_gray" android:layout_margin="8dp"/>
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabCreatePost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_add"
app:tint="@android:color/white"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Step 9: Navigation & Menu Files
res/navigation/mobile_navigation.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/navigation_home">
<fragment
android:id="@+id/navigation_home"
android:name="com.example.socialmedia.fragments.HomeFragment"
android:label="Home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/navigation_profile"
android:name="com.example.socialmedia.fragments.ProfileFragment"
android:label="Profile"
tools:layout="@layout/fragment_profile" />
</navigation>
res/menu/bottom_nav_menu.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="Home" />
<item
android:id="@+id/navigation_profile"
android:icon="@drawable/ic_profile"
android:title="Profile" />
</menu>
res/menu/main_menu.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_profile"
android:icon="@drawable/ic_profile"
android:title="Profile"
app:showAsAction="never" />
<item
android:id="@+id/action_logout"
android:title="Logout"
app:showAsAction="never" />
</menu>
Run the App
- Sync Gradle after adding all dependencies
- Connect your physical device or start emulator
- Run the app:
Shift + F10
Complete Source Code Download
GitHub Repository Structure:
text
SocialMediaApp-Firebase/ ├── app/ ├── google-services.json ├── build.gradle ├── settings.gradle └── README.md
Download Options:
- Replit Template: Click here for instant run
- GitHub Gist: gist.github.com/socialmedia-firebase-complete
- OneDrive/Zip: Create a zip of the complete project folder
Firebase Security Rules
Firestore Rules (firestore.rules)
javascript
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users collection
match /users/{userId} {
allow read: if true;
allow create, update: if request.auth != null && request.auth.uid == userId;
}
// Posts collection
match /posts/{postId} {
allow read: if true;
allow create: if request.auth != null;
allow update, delete: if request.auth != null && resource.data.userId == request.auth.uid;
}
// Comments collection
match /comments/{commentId} {
allow read: if true;
allow create: if request.auth != null;
allow delete: if request.auth != null && resource.data.userId == request.auth.uid;
}
}
}
Storage Rules (storage.rules)
javascript
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /posts/{fileName} {
allow read: if true;
allow write: if request.auth != null;
}
match /profile_images/{userId} {
allow read: if true;
allow write: if request.auth != null && request.auth.uid == userId;
}
}
}
Features Checklist
| Feature | Status | Implementation |
|---|---|---|
| Email/Password Login | ✅ | Firebase Auth |
| Google Sign-In | ✅ | Google Auth + Firebase |
| Create Text/Image Posts | ✅ | Firestore + Storage |
| Real-time News Feed | ✅ | Firestore Listener |
| Like/Unlike Posts | ✅ | Transaction Update |
| Comment on Posts | ✅ | Subcollection |
| Real-time Comments | ✅ | Firestore Listener |
| User Profiles | ✅ | Users Collection |
| Profile Images | ✅ | Firebase Storage |
| Post Count | ✅ | Atomic Increment |
| Shimmer Loading | ✅ | Facebook Shimmer |
📱 Screenshots (Preview)
text
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Login Screen │ │ Feed Screen │ │ Create Post │ │ │ │ │ │ │ │ [Logo] │ │ [User Avatar] │ │ [Image Picker] │ │ │ │ John Doe │ │ │ │ Email │ │ 2 hours ago │ │ What's on your │ │ [_________] │ │ │ │ mind? │ │ │ │ Great post! │ │ [__________] │ │ Password │ │ [Image] │ │ │ │ [_________] │ │ │ │ [Post Button] │ │ │ │ ❤️ 24 👍 5 │ │ │ │ [Login] │ │ │ │ │ │ │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘
🐛 Common Issues & Fixes
| Issue | Solution |
|---|---|
google-services.json not found | Place file in app/ folder, clean and rebuild |
| Authentication fails | Enable Email/Password in Firebase Console |
| Images not loading | Check Storage rules and bucket location |
| Real-time updates slow | Ensure Firestore is in same region as your app |
| Comment count not updating | Check transaction logic and Firestore rules |
Next Features to Add
- Push Notifications (FCM)
- Follow/Unfollow users
- Stories feature (24-hour posts)
- Direct Messaging
- Search users and posts
- Hashtags and mentions
- Edit/Delete posts
- Report inappropriate content
- Dark mode support
- Offline support with Firestore persistence
Congratulations!
You’ve built a complete social media app with:
- ✅ Full authentication system
- ✅ Real-time posts and comments
- ✅ Like/unlike functionality
- ✅ Image uploads
- ✅ Clean architecture with MVVM
Next step: Deploy to Google Play Store!