Building a Chat App with Kotlin and Firebase
In this tutorial, we will learn how to build a chat application using Kotlin and Firebase. Kotlin is a modern programming language that runs on the Java Virtual Machine (JVM) and is fully interoperable with Java. Firebase is a mobile and web application development platform that provides various tools and services, including a real-time database and user authentication. By combining Kotlin and Firebase, we can create a powerful and efficient chat app with real-time messaging and user authentication.
Introduction
What is Kotlin?
Kotlin is a statically typed programming language developed by JetBrains. It is designed to be concise, expressive, and safe. Kotlin can be used for developing Android apps, server-side applications, and more. With its modern features and seamless interoperability with Java, Kotlin has gained popularity among developers.
What is Firebase?
Firebase is a mobile and web application development platform provided by Google. It offers a range of tools and services that simplify the development process, including real-time database, authentication, cloud storage, and more. Firebase provides a scalable and reliable backend infrastructure, allowing developers to focus on building their applications.
Why use Kotlin and Firebase for building a chat app?
Kotlin and Firebase are a perfect combination for building a chat app. Kotlin's concise syntax and modern features make it easy to write clean and maintainable code. Firebase provides real-time database and user authentication services, which are essential for a chat app. With Firebase, we can easily handle real-time messaging, user authentication, and user presence tracking.
Setting up the Project
Creating a new Firebase project
Before we can start building our chat app, we need to create a new Firebase project. To do this, follow these steps:
- Go to the Firebase Console and sign in with your Google account.
- Click on the "Add Project" button and enter a name for your project.
- Select your country/region and agree to the terms of service.
- Click on the "Create Project" button to create your Firebase project.
Adding Firebase to the Kotlin project
Once you have created your Firebase project, you need to add Firebase to your Kotlin project. To do this, follow these steps:
- Open your Kotlin project in Android Studio.
- Click on the "Tools" menu and select "Firebase".
- In the Firebase Assistant panel, click on the "Authentication" option.
- Click on the "Connect to Firebase" button and select your Firebase project from the list.
- Follow the instructions to add the necessary Firebase dependencies to your project.
Configuring Firebase Authentication
To use Firebase Authentication in our chat app, we need to configure it. To do this, follow these steps:
- In the Firebase Console, go to the "Authentication" section.
- Click on the "Sign-in method" tab.
- Enable the "Email/Password" sign-in provider.
- Enable any other sign-in providers you want to support, such as Google or Facebook.
- Configure the necessary settings for each sign-in provider.
Designing the User Interface
Creating the chat screen layout
The first step in building our chat app is to design the user interface. We will start by creating the layout for the chat screen. To do this, create a new XML layout file and add the following code:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- Chat messages -->
<ListView
android:id="@+id/chatListView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:divider="@null"
android:dividerHeight="0dp"
android:stackFromBottom="true"/>
<!-- Message input -->
<EditText
android:id="@+id/messageInputEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type a message..."/>
<!-- Send button -->
<Button
android:id="@+id/sendButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send"/>
</LinearLayout>
This layout consists of a ListView to display the chat messages, an EditText for the user to input messages, and a Button to send the messages.
Implementing user authentication UI
Next, we need to implement the user authentication UI. We will create a separate activity for the authentication process. To do this, create a new Kotlin class and add the following code:
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
// Initialize Firebase Authentication
val auth = Firebase.auth
// Set up the sign-in button click listener
signInButton.setOnClickListener {
val email = emailEditText.text.toString()
val password = passwordEditText.text.toString()
// Sign in with email and password
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// User signed in successfully
// Start the chat activity
startActivity(Intent(this, ChatActivity::class.java))
finish()
} else {
// Sign in failed
// Display an error message
Toast.makeText(
this, "Authentication failed.",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
This code sets up the sign-in button click listener and handles the authentication process using Firebase Authentication. If the user signs in successfully, it starts the chat activity. Otherwise, it displays an error message.
Handling user input and displaying messages
Now, we need to handle user input and display the chat messages. To do this, open the ChatActivity class and add the following code:
class ChatActivity : AppCompatActivity() {
private lateinit var messagesAdapter: ArrayAdapter<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
// Initialize the messages adapter
messagesAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1)
chatListView.adapter = messagesAdapter
// Set up the send button click listener
sendButton.setOnClickListener {
val message = messageInputEditText.text.toString()
// Add the message to the database
val database = Firebase.database.reference
val newMessageRef = database.child("messages").push()
newMessageRef.setValue(message)
// Clear the input field
messageInputEditText.text.clear()
}
// Set up the database listener
val database = Firebase.database.reference.child("messages")
database.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
// Get the message value from the snapshot
val message = snapshot.getValue(String::class.java)
// Add the message to the adapter
if (message != null) {
messagesAdapter.add(message)
}
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
// Not used in this tutorial
}
override fun onChildRemoved(snapshot: DataSnapshot) {
// Not used in this tutorial
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
// Not used in this tutorial
}
override fun onCancelled(error: DatabaseError) {
// Not used in this tutorial
}
})
}
}
This code initializes the messages adapter, sets up the send button click listener to add messages to the database, and sets up a database listener to receive new messages in real-time. The messages are displayed in the ListView using the messages adapter.
Implementing Real-time Messaging
Setting up Firebase Realtime Database
To enable real-time messaging in our chat app, we need to set up the Firebase Realtime Database. To do this, follow these steps:
- In the Firebase Console, go to the "Database" section.
- Click on the "Create Database" button.
- Select the "Start in test mode" option and click on the "Next" button.
- Choose a location for your database and click on the "Done" button.
Sending and receiving messages
To send and receive messages in real-time, we need to update the code in the ChatActivity class. Replace the existing code with the following:
class ChatActivity : AppCompatActivity() {
private lateinit var messagesAdapter: ArrayAdapter<String>
private lateinit var database: DatabaseReference
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
// Initialize the messages adapter
messagesAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1)
chatListView.adapter = messagesAdapter
// Get a reference to the database
database = Firebase.database.reference.child("messages")
// Set up the send button click listener
sendButton.setOnClickListener {
val message = messageInputEditText.text.toString()
// Add the message to the database
val newMessageRef = database.push()
newMessageRef.setValue(message)
// Clear the input field
messageInputEditText.text.clear()
}
// Set up the database listener
database.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
// Get the message value from the snapshot
val message = snapshot.getValue(String::class.java)
// Add the message to the adapter
if (message != null) {
messagesAdapter.add(message)
}
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
// Not used in this tutorial
}
override fun onChildRemoved(snapshot: DataSnapshot) {
// Not used in this tutorial
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
// Not used in this tutorial
}
override fun onCancelled(error: DatabaseError) {
// Not used in this tutorial
}
})
}
}
This code updates the database reference to point to the "messages" node in the Firebase Realtime Database. It also updates the send button click listener to push new messages to the database using the updated reference.
Updating the UI in real-time
To update the UI in real-time when new messages are added to the database, we need to modify the code in the ChatActivity class. Replace the existing code with the following:
class ChatActivity : AppCompatActivity() {
private lateinit var messagesAdapter: ArrayAdapter<String>
private lateinit var database: DatabaseReference
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
// Initialize the messages adapter
messagesAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1)
chatListView.adapter = messagesAdapter
// Get a reference to the database
database = Firebase.database.reference.child("messages")
// Set up the send button click listener
sendButton.setOnClickListener {
val message = messageInputEditText.text.toString()
// Add the message to the database
val newMessageRef = database.push()
newMessageRef.setValue(message)
// Clear the input field
messageInputEditText.text.clear()
}
// Set up the database listener
database.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
// Get the message value from the snapshot
val message = snapshot.getValue(String::class.java)
// Add the message to the adapter
if (message != null) {
messagesAdapter.add(message)
messagesAdapter.notifyDataSetChanged()
}
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
// Not used in this tutorial
}
override fun onChildRemoved(snapshot: DataSnapshot) {
// Not used in this tutorial
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
// Not used in this tutorial
}
override fun onCancelled(error: DatabaseError) {
// Not used in this tutorial
}
})
}
}
This code adds a call to messagesAdapter.notifyDataSetChanged()
after adding a new message to the adapter. This ensures that the ListView is updated immediately when a new message is received.
Adding User Authentication
Implementing email/password authentication
To implement email/password authentication in our chat app, we need to update the LoginActivity class. Replace the existing code with the following:
class LoginActivity : AppCompatActivity() {
private lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
// Initialize Firebase Authentication
auth = Firebase.auth
// Set up the sign-in button click listener
signInButton.setOnClickListener {
val email = emailEditText.text.toString()
val password = passwordEditText.text.toString()
// Sign in with email and password
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// User signed in successfully
// Start the chat activity
startActivity(Intent(this, ChatActivity::class.java))
finish()
} else {
// Sign in failed
// Display an error message
Toast.makeText(
this, "Authentication failed.",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
This code initializes the Firebase Authentication instance and sets up the sign-in button click listener. It uses the signInWithEmailAndPassword()
method to authenticate the user using their email and password.
Integrating social media login
To integrate social media login in our chat app, we need to update the LoginActivity class. Replace the existing code with the following:
class LoginActivity : AppCompatActivity() {
private lateinit var auth: FirebaseAuth
private lateinit var googleSignInClient: GoogleSignInClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
// Initialize Firebase Authentication
auth = Firebase.auth
// 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)
// Set up the sign-in button click listener
signInButton.setOnClickListener {
val signInIntent = googleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == RC_SIGN_IN) {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
// Google Sign-In was successful
val account = task.getResult(ApiException::class.java)
firebaseAuthWithGoogle(account.idToken)
} catch (e: ApiException) {
// Google Sign-In failed
Toast.makeText(
this, "Authentication failed.",
Toast.LENGTH_SHORT
).show()
}
}
}
private fun firebaseAuthWithGoogle(idToken: String?) {
val credential = GoogleAuthProvider.getCredential(idToken, null)
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// User signed in successfully
// Start the chat activity
startActivity(Intent(this, ChatActivity::class.java))
finish()
} else {
// Sign in failed
// Display an error message
Toast.makeText(
this, "Authentication failed.",
Toast.LENGTH_SHORT
).show()
}
}
}
companion object {
private const val RC_SIGN_IN = 9001
}
}
This code configures the Google Sign-In options and sets up the sign-in button click listener to start the Google Sign-In flow. It uses the firebaseAuthWithGoogle()
method to authenticate the user using their Google account.
Securing user data with Firebase Security Rules
To secure user data in our chat app, we need to set up Firebase Security Rules. To do this, follow these steps:
- In the Firebase Console, go to the "Database" section.
- Click on the "Rules" tab.
- Replace the existing rules with the following code:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"messages": {
"$messageId": {
".validate": "newData.hasChildren(['sender', 'text']) && newData.child('sender').isString() && newData.child('text').isString() && newData.child('sender').val() == auth.uid"
}
}
}
}
These rules allow authenticated users to read and write data in the database. They also ensure that each message has a "sender" and "text" field, and the "sender" field matches the authenticated user's UID.
Handling User Presence
Tracking online/offline status
To track the online/offline status of users in our chat app, we need to update the code in the ChatActivity class. Replace the existing code with the following:
class ChatActivity : AppCompatActivity() {
private lateinit var messagesAdapter: ArrayAdapter<String>
private lateinit var database: DatabaseReference
private lateinit var presenceRef: DatabaseReference
private lateinit var connectedRef: DatabaseReference
private lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
// Initialize the messages adapter
messagesAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1)
chatListView.adapter = messagesAdapter
// Get a reference to the database
database = Firebase.database.reference.child("messages")
// Get a reference to the user's presence status
auth = Firebase.auth
presenceRef = Firebase.database.reference.child("users").child(auth.currentUser?.uid ?: "")
// Get a reference to the connection status
connectedRef = Firebase.database.reference.child(".info/connected")
// Set up the send button click listener
sendButton.setOnClickListener {
val message = messageInputEditText.text.toString()
// Add the message to the database
val newMessageRef = database.push()
newMessageRef.setValue(message)
// Clear the input field
messageInputEditText.text.clear()
}
// Set up the database listener
database.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
// Get the message value from the snapshot
val message = snapshot.getValue(String::class.java)
// Add the message to the adapter
if (message != null) {
messagesAdapter.add(message)
messagesAdapter.notifyDataSetChanged()
}
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
// Not used in this tutorial
}
override fun onChildRemoved(snapshot: DataSnapshot) {
// Not used in this tutorial
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
// Not used in this tutorial
}
override fun onCancelled(error: DatabaseError) {
// Not used in this tutorial
}
})
// Set up the presence listener
connectedRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val connected = snapshot.getValue(Boolean::class.java) ?: false
if (connected) {
// User is online
presenceRef.setValue(true)
presenceRef.onDisconnect().setValue(false)
} else {
// User is offline
presenceRef.setValue(false)
}
}
override fun onCancelled(error: DatabaseError) {
// Not used in this tutorial
}
})
}
}
This code adds a reference to the user's presence status and the connection status. It then sets up a listener for the connection status and updates the user's presence status accordingly.
Displaying user presence in the chat
To display the user presence in the chat, we need to update the code in the ChatActivity class. Replace the existing code with the following:
class ChatActivity : AppCompatActivity() {
private lateinit var messagesAdapter: ArrayAdapter<String>
private lateinit var database: DatabaseReference
private lateinit var presenceRef: DatabaseReference
private lateinit var connectedRef: DatabaseReference
private lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
// Initialize the messages adapter
messagesAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1)
chatListView.adapter = messagesAdapter
// Get a reference to the database
database = Firebase.database.reference.child("messages")
// Get a reference to the user's presence status
auth = Firebase.auth
presenceRef = Firebase.database.reference.child("users").child(auth.currentUser?.uid ?: "")
// Get a reference to the connection status
connectedRef = Firebase.database.reference.child(".info/connected")
// Set up the send button click listener
sendButton.setOnClickListener {
val message = messageInputEditText.text.toString()
// Add the message to the database
val newMessageRef = database.push()
newMessageRef.setValue(message)
// Clear the input field
messageInputEditText.text.clear()
}
// Set up the database listener
database.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
// Get the message value from the snapshot
val message = snapshot.getValue(String::class.java)
// Add the message to the adapter
if (message != null) {
messagesAdapter.add(message)
messagesAdapter.notifyDataSetChanged()
}
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
// Not used in this tutorial
}
override fun onChildRemoved(snapshot: DataSnapshot) {
// Not used in this tutorial
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
// Not used in this tutorial
}
override fun onCancelled(error: DatabaseError) {
// Not used in this tutorial
}
})
// Set up the presence listener
connectedRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val connected = snapshot.getValue(Boolean::class.java) ?: false
if (connected) {
// User is online
presenceRef.setValue(true)
presenceRef.onDisconnect().setValue(false)
} else {
// User is offline
presenceRef.setValue(false)
}
}
override fun onCancelled(error: DatabaseError) {
// Not used in this tutorial
}
})
// Set up the presence indicator
val presenceRef = Firebase.database.reference.child("users").child(auth.currentUser?.uid ?: "")
presenceRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val online = snapshot.getValue(Boolean::class.java) ?: false
if (online) {
// User is online
presenceImageView.setImageResource(R.drawable.ic_online)
} else {
// User is offline
presenceImageView.setImageResource(R.drawable.ic_offline)
}
}
override fun onCancelled(error: DatabaseError) {
// Not used in this tutorial
}
})
}
}
This code adds a reference to the user's presence status and sets up a listener to update the presence indicator image view based on the user's online/offline status.
Conclusion
In this tutorial, we have learned how to build a chat app using Kotlin and Firebase. We started by setting up the project and configuring Firebase Authentication. Then, we designed the user interface for the chat screen and implemented real-time messaging using Firebase Realtime Database. We also added user authentication with email/password and social media login. Lastly, we implemented user presence tracking to display the online/offline status of users in the chat.
By following this tutorial, you have learned the basics of building a chat app with Kotlin and Firebase. You can now explore more advanced features and customize the app to suit your needs. Happy coding!