Build a modern Android chat app with Kotlin
source link: https://www.tuicool.com/articles/hit/UzQFNvi
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Ever heard of the phrase “Communication is key”?
Communication plays an important role in solving problems in the world. Every day, we always strive on how to develop a faster or better way to communicate. To make us feel closer to one another. To send information all over the world. One of the companies working on these solutions is CometChat
CometChat is a platform that helps you quickly integrate voice, video, and text messaging features into your web and mobile apps.
In this tutorial, we’ll build a simple Android chat application powered by CometChat:
I encourage you follow along but if you’d rather to skip ahead to the code, you can find the complete code for this application on GitHub .
Create an Android Studio Project
Start a new Android Studio Project and select Empty Activity :
Enter a name for your application, I called mine “CometChat” and leave This project will support instant apps and Use AndroidX artifacts unchecked:
Setup CometChat in Our App
Let’s first install the CometChat Pro SDK for us to use its services.
1. Open your build.gradle
file and add these URLs:
allprojects { repositories { google() jcenter() maven { url "https://dl.bintray.com/cometchat/pro" } maven { url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases" } } }
For us to download the CometChat SDK, we must first add these repositories.
2. In the same build.gradle
file, add these lines inside android
block
android { compileSdkVersion 28 defaultConfig { ... } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } buildTypes { ... } }
The CometChat SDK uses Java 8 language features so we must configure our project to enable it.
3. Also in build.gradle file, add these lines inside dependencies
block
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// Support implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:design:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support:recyclerview-v7:28.0.0'
// CometChat implementation 'com.cometchat:pro-android-chat-sdk:1.0.+'
// Test testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }
4. Click Sync Now in the top-right-hand corner
Get UI Stuff out of the Way
Now we have installed our dependencies, let’s define some UI-related values ahead of time.
1. Open app/res/values
2. Replace color.xml
:
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#673AB7</color> <color name="colorPrimaryDark">#512DA8</color> <color name="colorAccent">#E040FB</color> <color name="gray">#DDDDDD</color> </resources>
This is where we set the colors for our app. If you’d like, you can customise it to any colours you want. For inspiration, check out Material Palette.
3. Edit styles.xml
:
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style>
</resources>
Replace your parent
theme to use the material design theme that we imported using implementation 'com.android.support:design:28.0.0
‘ when we were setting up CometChat in our app.
4. Edit strings.xml
Your strings.xml
file will contain your CometChat app ID and API key, which we will create in the next section:
<resources> <string name="app_name">CometChatPro</string> <string name="username">Username</string> <string name="join_chat">Join Chat</string> <string name="send">Send</string> <string name="enter_message">Enter message…</string> <string name="appID">YOUR_APP_ID_HERE</string> <string name="apiKey">YOUR_API_KEY_HERE</string> </resources>
Again, don’t worry about YOUR_APP_ID_HERE and YOUR_API_KEY_HER E for now. We will change that in the next step.
Create and Setup a CometChat Pro Account
1. If you haven’t already, create a CometChat account
2. After you have signed up and logged in, head over to your dashboard
3. Create a new app called “CometChatPro”
4. Create an API Key:
5. Go back to your strings.xml
and paste your app ID and API key
<resources> ... <string name="appID">2179fddc591bf</string> <string name="apiKey">6cb3aa84e2c066ed8bb34d3a58b3d6904d829b6e</string> </resources>
Now that we have all these setup, let’s test if we can initiate a connection to CometChat Pro API.
Initialize CometChat in Our App
Now that we have an API key created, let’s initialize the CometChat SDK in our app to make a connection with the CometChat servers.
1. Create a new class called App.kt
under cometchat/cometchatpro
and paste the following code:
import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import com.cometchat.pro.models.BaseMessage import com.cometchat.pro.models.TextMessage
class MessagesAdapter(private val uid: String, private var messages: MutableList<BaseMessage>) : RecyclerView.Adapter<MessagesAdapter.MessageViewHolder>() {
companion object { private const val SENT = 0 private const val RECEIVED = 1 }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder { val view = when (viewType) { SENT -> { LayoutInflater.from(parent.context).inflate(R.layout.item_sent, parent, false) } else -> { LayoutInflater.from(parent.context).inflate(R.layout.item_received, parent, false) } } return MessageViewHolder(view) }
override fun getItemCount() = messages.size
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) { holder.bind(messages[position]) }
override fun getItemViewType(position: Int): Int { return if (messages[position].sender?.uid!!.contentEquals(uid)) { SENT } else { RECEIVED } }
fun updateMessages(messages: List<BaseMessage>) { this.messages = messages.toMutableList() notifyDataSetChanged() }
fun appendMessage(message: BaseMessage) { this.messages.add(message) notifyItemInserted(this.messages.size - 1) }
inner class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val messageText: TextView = itemView.findViewById(R.id.message_text)
fun bind(message: BaseMessage) { if (message is TextMessage) { messageText.text = message.text } } } }
4. Open AndroidManifest.xml
and add our newly created App class:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.cometchat.cometchatpro"> <application android:name=".App" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> ... </application> </manifest>
Run the app in an emulator or your own device. Check the logs and you should see the text “Initialization completed: Init Successful”:
Now that we’re connected with the CometChat Pro service, it’s time to for us to interact with it.
In every chat app, there are users involved who are communicating with one another. So let’s first create a login screen to authenticate our user.
Create Our Authentication UI
For each user of your app, you need to create a CometChat user. Each user has a username and a unique ID (UID). Optionally, you can associate additional metadata like a profile picture with the user but that isn’t something we’ll talk about here. You can read more about CometChat users in the official documentation .
CometChat users can be created with code using the CreateUser API . However, to keep things really simple, we will instead create users through the CometChat dashboard.
Since we will create a user manually, we’ll only use the UID of the user to log in. We’ll create one text field for our UID and a button for logging in.
1. Under the res/layout folder, open activity_main.xml
and paste the following code:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout 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">
<EditText android:id="@+id/username" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginTop="8dp" android:layout_marginEnd="24dp" android:layout_marginBottom="8dp" android:hint="@string/username" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
<Button android:id="@+id/join_chat" android:layout_width="128dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@string/join_chat" app:layout_constraintEnd_toEndOf="@+id/username" app:layout_constraintStart_toStartOf="@+id/username" app:layout_constraintTop_toBottomOf="@+id/username" /> </android.support.constraint.ConstraintLayout>
2. Run the app and you should have something similar to this
Implement Our Authentication Code
After you’ve created your UI for authentication, let’s add the functionality for logging in by authenticating with the CometChat servers.
1. Open your MainActivity.class
and paste the following code:
class MainActivity : AppCompatActivity() {
private lateinit var join: Button private lateinit var username: EditText
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
username = findViewById(R.id.username)
join = findViewById(R.id.join_chat) join.setOnClickListener { disableAuthField() login() } }
private fun disableAuthField() { join.isEnabled = false username.isEnabled = false }
private fun login() { CometChat.login(username.text.toString(), getString(R.string.apiKey), object : CometChat.CallbackListener() { override fun onSuccess(user: User) { username.setText("") enableAuthField() Toast.makeText(this@MainActivity, "Login successful", Toast.LENGTH_SHORT).show() }
override fun onError(e: CometChatException) { Toast.makeText(this@MainActivity, "Error or username doesn't exist.", Toast.LENGTH_SHORT).show() enableAuthField() } }) }
private fun enableAuthField() { join.isEnabled = true username.isEnabled = true } }
But wait! You might ask yourself “How do I create a user?”. To make this tutorial simple, we will create a user using the dashboard . For an actual production app, you should create users using the CreateUser API .
Creating a User
Go to your dashboard and click the Explore button beneath your CometChatPro app
As you can see, CometChat provides a set of existing sample users. You can also use this one but creating a user is also very simple.
Take note of the UID of the user you just created as that’s what we’ll use to login to our app.
Logging in to CometChat
Use the UID `user1` of the user that we just created.
1. Run the app
2. In the username input, enter “user1”
3. Click Login and you should see a toast saying “Login successful”
Now that we can login, let’s move on to the best part of this tutorial, developing the chat feature!
We’ll learn how to send, fetch and listen for messages in real-time using CometChat. :sunglasses:
Create a Chat Room Using CometChat Groups
It goes by many names — conversation, chat room, or group. What’s important is that for users to communicate with each other, they must have the means to deliver those messages to one another.
That’s where CometChat Groups come in. Think of groups like a chat room for users to send their messages and for others in that group to receive the messages also.
For the purposes of this tutorial, we’ll make a group using our CometChat dashboard. You can also create a group dynamically with code if you want as well. More information on that here .
1. In your sidebar, click Groups :
2. CometChat provides you with a sample group called “supergroup”. Creating your own group is also easy.
3. Click Create Group
4. Enter the GUID, Name, and Type
5. Click Create Group and take note GUID as we will use it later in the steps below
Create Our Messages Screen UI
Now that we’ve created a group, we’ll create our UI for sending and showing chat messages.
1. Create a new empty activity called MessagesActivity
under cometchat/cometchatpro
2. Open your activity_messages.xml
layout and paste the following code:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout 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.support.v7.widget.RecyclerView android:id="@+id/messages" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toTopOf="@+id/enter_message" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
<EditText android:id="@+id/enter_message" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" android:hint="@string/enter_message" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/send_message" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" />
<Button android:id="@+id/send_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:text="@string/send" app:layout_constraintBottom_toBottomOf="@+id/enter_message" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/enter_message" app:layout_constraintTop_toTopOf="@+id/enter_message" />
</android.support.constraint.ConstraintLayout>
3. Open your MainActivity.class
and start our MessagesActivity
if login is successful
private fun login() { CometChat.login(username.text.toString(), getString(R.string.apiKey), object : CometChat.CallbackListener() { override fun onSuccess(user: User) { username.setText("") enableAuthField() val intent = Intent(this@MainActivity, MessagesActivity::class.java) startActivity(intent) } ... }) }
Run the app and it should start our MessagesActivity
when we login as “user1”
Sending a Message
In CometChat, there are two types of messages: TextMessage and MediaMessage . For this tutorial, we will focus only on TextMessages.
1. Under the res/drawable folder, create two new drawables:
item_received_background.xml
:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/gray" /> <corners android:radius="4dp" /> </shape>
item_sent_background.xml
:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/colorPrimary" /> <corners android:radius="4dp" /> </shape>
2. Under the res/layout folder, create two new layouts:
item_received.xml
:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/message_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" android:background="@drawable/item_received_background" android:gravity="center" android:padding="8dp" /> </LinearLayout>
item_sent.xml
:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/message_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end" android:layout_margin="8dp" android:background="@drawable/item_sent_background" android:gravity="center" android:padding="8dp" android:textColor="@android:color/white" /> </LinearLayout>
3. Create a new class — MessagesAdapter
and paste the following code:
import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import com.cometchat.pro.models.BaseMessage import com.cometchat.pro.models.TextMessage
class MessagesAdapter(private val uid: String, private var messages: MutableList<BaseMessage>) : RecyclerView.Adapter<MessagesAdapter.MessageViewHolder>() {
companion object { private const val SENT = 0 private const val RECEIVED = 1 }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder { val view = when (viewType) { SENT -> { LayoutInflater.from(parent.context).inflate(R.layout.item_sent, parent, false) } else -> { LayoutInflater.from(parent.context).inflate(R.layout.item_received, parent, false) } } return MessageViewHolder(view) }
override fun getItemCount() = messages.size
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) { holder.bind(messages[position]) }
override fun getItemViewType(position: Int): Int { return if (messages[position].sender?.uid!!.contentEquals(uid)) { SENT } else { RECEIVED } }
fun updateMessages(messages: List<BaseMessage>) { this.messages = messages.toMutableList() notifyDataSetChanged() }
fun appendMessage(message: BaseMessage) { this.messages.add(message) notifyItemInserted(this.messages.size - 1) }
inner class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val messageText: TextView = itemView.findViewById(R.id.message_text)
fun bind(message: BaseMessage) { if (message is TextMessage) { messageText.text = message.text } } } }
4. Open your MessagesActivity.class
and paste the following code. Be sure to set roomID
field as “androidroom”, which we created earlier:
import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.util.Log import android.widget.Button import android.widget.EditText import com.cometchat.pro.constants.CometChatConstants import com.cometchat.pro.core.CometChat import com.cometchat.pro.exceptions.CometChatException import com.cometchat.pro.models.TextMessage
class MessagesActivity : AppCompatActivity() {
private lateinit var enterMessage: EditText private lateinit var send: Button private lateinit var messagesList: RecyclerView private lateinit var messagesAdapter: MessagesAdapter
private val roomID = "androidroom"
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_messages)
enterMessage = findViewById(R.id.enter_message) send = findViewById(R.id.send_message)
messagesList = findViewById(R.id.messages) val layoutMgr = LinearLayoutManager(this) layoutMgr.stackFromEnd = true messagesList.layoutManager = layoutMgr
messagesAdapter = MessagesAdapter(CometChat.getLoggedInUser().uid, mutableListOf()) messagesList.adapter = messagesAdapter
send.setOnClickListener { sendMessage() } joinGroup() }
private fun joinGroup() { CometChat.joinGroup( roomID, CometChatConstants.GROUP_TYPE_PUBLIC, "", object : CometChat.CallbackListener<String>() { override fun onSuccess(successMessage: String) { Log.d("CometChat", "Group join successful") }
override fun onError(e: CometChatException) { e.code?.let { // For now, we'll just keep on attempting to join the group // because persistence is out of the scope for this tutorial if (it.contentEquals("ERR_ALREADY_JOINED")) { Log.d("CometChat", "Already joined the group") } } } }) }
private fun sendMessage() { val textMessage = TextMessage( roomID, enterMessage.text.toString(), CometChatConstants.MESSAGE_TYPE_TEXT, CometChatConstants.RECEIVER_TYPE_GROUP )
CometChat.sendMessage(textMessage, object : CometChat.CallbackListener<TextMessage>() { override fun onSuccess(message: TextMessage) { enterMessage.setText("") messagesAdapter.appendMessage(message) scrollToBottom() }
override fun onError(e: CometChatException) { Log.d("CometChat", "Message send failed: ${e.message}") } }) }
private fun scrollToBottom() { messagesList.scrollToPosition(messagesAdapter.itemCount - 1) } }
Take note of the joinGroup()
function.
Before we can send a message to a group, we must first join the group. Ignore the onError(…)
part for now as we will attempt to keep joining the group because persistence is out of our scope for now.
5. Run the app and try sending a message. You should have something like this.
Pretty awesome right?! :wink:
But there’s a problem. If you kill the app and login again, the screen is empty. Where did our message go? Proceed to the next step to find out
Fetching Previous Messages
In the previous step, we were able send a message but when we open the app again, the screen is empty.
Don’t worry, our message was really sent and it’s stored in CometChat. We just need a way to fetch the messages that was sent in the group.
1. Open your MessagesActivity.class
and add the fetchMessages()
function
class MessagesActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { ... }
private fun joinGroup() { CometChat.joinGroup( roomID, CometChatConstants.GROUP_TYPE_PUBLIC, "", object : CometChat.CallbackListener<String>() { override fun onSuccess(successMessage: String) { fetchMessages() }
override fun onError(e: CometChatException) { e.code?.let { // For now, we'll just keep on attempting to join the group // because persistence is out of the scope for this tutorial if (it.contentEquals("ERR_ALREADY_JOINED")) { fetchMessages() } } } }) }
private fun fetchMessages() { val messagesRequest = MessagesRequest.MessagesRequestBuilder() .setGUID(roomID) .setLimit(30) .build()
messagesRequest.fetchPrevious(object : CometChat.CallbackListener<List<BaseMessage>>() { override fun onSuccess(messages: List<BaseMessage>) { messagesAdapter.updateMessages(messages.filter { it is TextMessage }) scrollToBottom() }
override fun onError(e: CometChatException) { Log.d("CometChat", "Fetch messages failed: ${e.message}") } }) } ... }
2. Run the app and you should see the message that you sent a while ago :tada:
I know what you’re thinking.
“Dude, I know I can now send a message and see my messages. But talking to myself would be kinda weird right?”
Worry not! In the next step, we will learn how to receive messages from other, real people in real-time!
Receive messages in real-time!
1. Modify our MessagesActivity.class
to listen for new messages:
class MessagesActivity : AppCompatActivity() { ... private val listenerID = "MESSAGES_LISTENER"
override fun onCreate(savedInstanceState: Bundle?) { ... }
override fun onResume() { super.onResume() CometChat.addMessageListener(listenerID, object : CometChat.MessageListener() { override fun onTextMessageReceived(message: TextMessage) { messagesAdapter.appendMessage(message) scrollToBottom() } }) }
override fun onPause() { super.onPause() CometChat.removeMessageListener(listenerID) } ... }
That’s it?! Yes, that’s how easy it is to receive messages from other users real-time in CometChat.
2. Run the app in two emulators or devices. Log in the first one as “user1” and the other as “superhero1” — one of the sample users provided by CometChat. This is what you should see:
Congratulations :tada:! You just have made your first ever Android chat application! Give yourself a pat in the back. Reward yourself. Share it with your friends and if you have time, teach them also how to make one.
If you’ve made it this far, We’d like to say thank you so much for taking the time to really complete the tutorial.
We hope that you’ve learned something and at least have an impact on your journey as an Android developer.
Where to Next?
This tutorial is just the tip of the iceberg of what you can do with CometChat. To improve upon what you’ve made, here are some resources that can help you with that:
CometChat Pro Android Documentation
Check out CometChat Pro for other platforms as well.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK