Chat Application in Android Jetpack Compose
Last Updated :
21 Oct, 2024
Chat Application are common application across Android world leading tech companies are directly or indirectly involved in such kind of Application. In this article, we will Create a Chat Application Using Android Jetpack Compose and integrate it with Firebase to enable real-time Message exchange.
Prerequisites
Before proceeding with this tutorial, make sure you have:
- Basic knowledge of Kotlin and Jetpack Compose.
- Android Studio (Arctic Fox or later) Installed.
- Firebase project setup with Firestore or Realtime Database / Email and password based Authentication.
Project Setup
Step 1: Create a New Android Project
- Open Android Studio.
- Click on New Project > Select Empty Compose Activity.
- Set the Application Name (e.g., FirebaseChatApp).
- Select Kotlin as the programming language.
- Click Finish to create the project.
Step 2: Add Firebase Dependencies
To connect your application with Firebase, you need to add Firebase SDK dependencies.
Open your app-level build.gradle and add the following dependencies:
Kotlin
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
// Jetpack Compose
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
// Material Design
implementation(libs.androidx.material3)
// Firebase dependencies
implementation(platform("com.google.firebase:firebase-bom:33.4.0"))
implementation("com.google.firebase:firebase-firestore") // Firestore
implementation("com.google.firebase:firebase-auth-ktx")
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
// Testing
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
// Debugging & UI Tooling
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}
Step 3: Configure Firebase
- Go to the Firebase Console.
- Create a new Firebase project.
- Add an Android app to your project, and follow the steps to register the app.
- Download the google-services.json file and place it in your Android project’s app/ directory.
- In your build.gradle (project-level), add the following:
// Top-level build file where you can add configuration
// options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
id("com.google.gms.google-services") version "4.4.2" apply false
}
In your app-level build.gradle, add the following at the bottom:
id("com.google.gms.google-services")
Step 4.1: Firebase Firestore Setup
In Firebase, set up Firestore as your database:
- Go to Cloud Firestore in the Firebase console.
- Click Create database.
- Select Start in test mode to allow read and write access.
Now, you're ready to use Firebase Firestore in your app.
Step 4.2: Firebase Authentication Setup
In Firebase, set up Email and password based authentication:
- Go to Authentication in the Firebase console.
- Click Email and Password based authentication.
Now, you're ready to use Firebase Authentication in your app.
Directory Structure of the Chat Application
Implementing the Chat Application
Step 5: Modify Code to Use Firebase
Now, we will update the chat app to store and retrieve messages from Firestore in real time and add Email and password based authentication.
Here’s the updated code for SignupActivity.kt:
SignupActivity.kt
package com.nikhil.chatapplication
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.platform.LocalContext
import com.google.firebase.auth.FirebaseAuth
class SignupActivity : ComponentActivity() {
private lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
auth = FirebaseAuth.getInstance()
setContent {
SignupScreen()
}
}
}
@Composable
fun SignupScreen() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var confirmPassword by remember { mutableStateOf("") }
var errorMessage by remember { mutableStateOf<String?>(null) }
val auth = FirebaseAuth.getInstance()
// Get the context for launching activities
val context = LocalContext.current
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Register", fontSize = 32.sp, color = Color.Black)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
modifier = Modifier.fillMaxWidth(),
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = confirmPassword,
onValueChange = { confirmPassword = it },
label = { Text("Confirm Password") },
modifier = Modifier.fillMaxWidth(),
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
if (password == confirmPassword) {
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
// Redirect to login after successful signup
val intent = Intent(context, LoginActivity::class.java)
context.startActivity(intent)
} else {
errorMessage = task.exception?.message
}
}
} else {
errorMessage = "Passwords do not match"
}
}
) {
Text(text = "Register")
}
if (errorMessage != null) {
Text(text = errorMessage!!, color = Color.Red)
}
Spacer(modifier = Modifier.height(16.dp))
// Button to navigate to Login Screen
TextButton(onClick = {
val intent = Intent(context, LoginActivity::class.java)
context.startActivity(intent)
}) {
Text(text = "Already have an account? Login here", color = Color.Blue)
}
}
}
LoginActivity File:
Login Page is created using LoginActivity.kt file. Here’s the updated code for LoginActivity.kt:
LoginActivity.kt
package com.nikhil.chatapplication
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.firebase.auth.FirebaseAuth
import androidx.compose.ui.platform.LocalContext
class LoginActivity : ComponentActivity() {
private lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
auth = FirebaseAuth.getInstance()
setContent {
LoginScreen()
}
}
}
@Composable
fun LoginScreen() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var errorMessage by remember { mutableStateOf<String?>(null) }
val auth = FirebaseAuth.getInstance()
// Get the context for launching activities
val context = LocalContext.current
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// "LOGIN" Heading
Text(text = "LOGIN", fontSize = 32.sp, color = Color.Black)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
modifier = Modifier.fillMaxWidth(),
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
val intent = Intent(context, MainActivity::class.java)
context.startActivity(intent)
// Optionally finish() if inside an activity
} else {
errorMessage = task.exception?.message
}
}
}) {
Text(text = "Login")
}
if (errorMessage != null) {
Text(text = errorMessage!!, color = Color.Red)
}
Spacer(modifier = Modifier.height(16.dp))
// Button to navigate to Signup Screen
TextButton(onClick = {
val intent = Intent(context, SignupActivity::class.java)
context.startActivity(intent)
}) {
Text(text = "Don't have an account? Register here", color = Color.Blue)
}
}
}
MainActivity File:
MainActivity is the file which acts the main page where everyone can send their messages. Here’s the updated code for MainActivity.kt:
MainActivity.kt
package com.nikhil.chatapplication
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.*
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ChatApp()
}
}
}
@Composable
fun ChatApp() {
var messageText by remember { mutableStateOf(TextFieldValue("")) }
var messages by remember { mutableStateOf(listOf<Message>()) }
val db = FirebaseFirestore.getInstance()
val auth = FirebaseAuth.getInstance()
val currentUser = auth.currentUser
val scope = rememberCoroutineScope()
// Fetch messages from Firestore
LaunchedEffect(Unit) {
db.collection("messages")
.orderBy("timestamp")
.addSnapshotListener { snapshot, e ->
if (e == null && snapshot != null) {
val fetchedMessages = snapshot.documents.map {
Message(
message = it.getString("message") ?: "",
senderId = it.getString("senderId") ?: "",
timestamp = it.getTimestamp("timestamp")?.toDate().toString()
)
}
messages = fetchedMessages
}
}
}
Column(modifier = Modifier.fillMaxSize().padding(8.dp)) {
LazyColumn(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
contentPadding = PaddingValues(8.dp)
) {
items(messages.size) { index ->
MessageItem(
message = messages[index],
isCurrentUser = messages[index].senderId == currentUser?.uid
)
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
BasicTextField(
value = messageText,
onValueChange = { messageText = it },
modifier = Modifier
.weight(1f)
.background(Color.LightGray, CircleShape)
.padding(12.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Button(
onClick = {
if (messageText.text.isNotBlank()) {
scope.launch {
val messageData = mapOf(
"message" to messageText.text,
"senderId" to currentUser?.uid,
"timestamp" to com.google.firebase.Timestamp.now()
)
db.collection("messages").add(messageData)
messageText = TextFieldValue("")
}
}
}
) {
Text(text = "Send")
}
}
}
}
@Composable
fun MessageItem(message: Message, isCurrentUser: Boolean) {
val alignment = if (isCurrentUser) Alignment.End else Alignment.Start
val backgroundColor = if (isCurrentUser) MaterialTheme.colorScheme.primary else Color.Gray
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalAlignment = alignment
) {
Surface(
shape = MaterialTheme.shapes.medium,
shadowElevation = 1.dp,
color = backgroundColor
) {
Text(
text = "${message.message}\n${message.timestamp}",
fontSize = 16.sp,
color = Color.White,
modifier = Modifier.padding(12.dp)
)
}
}
}
data class Message(
val message: String,
val senderId: String,
val timestamp: String
)
Explanation of the above Application:
- Firestore: We connect to Firestore using FirebaseFirestore.getInstance() and use the collection named "messages" with senderId and timestamp.
- addSnapshotListener: This listens for real-time updates from Firestore. Whenever new messages are added, the UI updates automatically.
- messageText: When the user types a message and presses send, the message gets stored in Firestore using the add method.
- Authentication: Helps in recognizing the messages of each user.
Running the Application
- Connect an Android device or emulator.
- Click Run in Android Studio.
- Type a message, and it will be stored in Firebase Firestore.
- All users will see the new messages in real-time.
Data Flow Diagram (DFD)
Here's a simple Data Flow Diagram (DFD) for the chat application:
Level 0 DFD (Context Diagram)
Level 1 DFD (Detailed Diagram)
Output Video
Whole Application Code GitHub Link:
Conclusion
In this tutorial, we built a chat application using Jetpack Compose and Firebase Firestore. With Firebase’s real-time capabilities, messages are instantly synced across all devices, making the chat app dynamic and real-time. This basic application can be extended further by adding user authentication, timestamps, and read receipts.
Similar Reads
Canvas API in Android Jetpack Compose
Jetpack Compose is a modern UI toolkit that is designed to simplify UI development in Android. It consists of a reactive programming model with conciseness and ease of Kotlin programming language. It is fully declarative so that you can describe your UI by calling some series of functions that will
4 min read
Android Jetpack Compose - Close Application By Button
When we are implementing an AlertDialog within the android application for asking for permissions within the android application. In that case, we provide 2 options either to grant permissions or close the application. In this article, we will take a look at How to quit an android application progra
2 min read
Lottie Animation in Android Jetpack Compose
Lottie is a great library to add animated files into your app. Two days ago Jetpack compose went stable and also Lottie supports Compose. In this article, we are going to see how to add Lottie animations in compose app.What are we going to build in this article?We will build a simple app showing Lot
6 min read
Card in Android Jetpack Compose
In Jetpack Compose, a Card serves as a modern alternative to CardView in traditional Android development. It acts as a container that can hold multiple UI elements, making it ideal for structuring content in a visually appealing way. The Card component includes an elevation property, which adds a sh
2 min read
Change Button Size in Android Jetpack Compose
In Android, Button is a very common UI element that is used to call a function or perform a task when clicked by the user. A Button by default occupies specific space depending upon the text that it displays. If the Button text is longer or has a larger font size, the Button width, and height increa
3 min read
Basics of Jetpack Compose in Android
Jetpack Compose is a modern UI toolkit that is designed to simplify UI development in Android. It consists of a reactive programming model with conciseness and ease of Kotlin programming language. It is fully declarative so that you can describe your UI by calling some series of functions that will
5 min read
Send Email in an Android Application using Jetpack Compose
Many times while building an android application. We have to send a mail to any specific address from our android application. Inside this mail, we have to define the email address, subject, and body to be sent over Gmail or any other mail application. In this article, we will be building a simple a
7 min read
Speech to Text Application in Android using Jetpack Compose
Speech to Text is used in most applications such as Google Search for searching any query. For using this feature user simply has to tap on the microphone icon and speak the query he wants to search. The speech of the user will be converted to text. In this article, we will take a look at How we can
7 min read
Button in Android using Jetpack Compose
Jetpack Compose is a new toolkit provided by Google. This is useful for designing beautiful UI designs. A Button is a UI component in Android which is used to navigate between different screens. With the help of a button, the user can interact with your app and perform multiple actions inside your a
3 min read
Jetpack Architecture Components in Android
Android Jetpack is a set of software components, libraries, tools, and guidance to help in developing robust Android applications. Launched by Google in 2018, Jetpack comprises existing android support libraries, android architecture components with an addition of the Android KTX library as a single
10 min read