When I last built a chat, it took me almost a week and still - there were finicky ongoing issues that required my attention! With CometChat, I managed to build a complete and reliable Android chat in under 3 hours, and now I'm here to teach you to do the same. CometChat has all the features you'd expect as well as has excellent documentation and tutorials (like this one) to help you with your implementation.

By the end of this tutorial, you should be able to implement a semi-complex Android group chat. We'll start by initializing the CometChat Android SDK, then move to logging the user in, fetching the list of groups, creating a new one, joining groups you weren't a part of and finally, the chat itself. CometChat is built for developers to make the task of implementing a chat much quicker, easier, and more reliable.

What we'll build

Here is a preview of what we'll build:

As you can see, the UI is pretty neat and clean resembling all the other chat apps you might have seen. Your messages appear on the right in blue, while others' messages are on the left in white.

I encourage you to follow along but you can also download the complete code on GitHub. I took care to write instructions in the README.

How to build an Android chat

The first thing we need to do is follow the docs to kick-start our chat app using CometChat. You can create a free CometChat account.

  1. Get your applications keys after signing up for CometChat. It is very easy to create a free account and get started. Signup for CometChat and then:
    a. Create a new app here

    b. Click "Explore" and then head over to the API Keys section and click on the Create API Key button

    c. Enter a name and select the scope as Auth Only

    d. Now note the API Key and App ID

  2. Add the CometChat dependency in your Gradle files
    a. First, add the repository URL to the project level build.gradle file in the repositories block under the allprojects section:

    allprojects {
      repositories {
        maven {
          url "https://dl.bintray.com/cometchat/pro"
        }
        maven {
          url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases"
        }
      }
    }
    

    b. Then, add the CometChat SDK to the app level build.gradle file in the dependenciessection:

    dependencies {
      implementation 'com.cometchat:pro-android-chat-sdk:1.5.+'
    }
    

    c. Then add the following lines to defaultConfig section of the app level gradle file:

    defaultConfig {
      ndk {
        abiFilters "armeabi-v7a", "x86"
      }
    }
    

    d. Finally, add the below lines android section of the app level gradle file:

     android {
       compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_8
         targetCompatibility JavaVersion.VERSION_1_8
     }
    
  3. Next, initialize CometChat in the code in WelcomeActivity.kt:

    val appID:String="APP_ID"
    
    CometChat.init(this,appID, object : CometChat.CallbackListener<String>() {
      override fun onSuccess(p0: String?) {
         Log.d(TAG, "Initialization completed successfully")
       }
    
       override fun onError(p0: CometChatException?) {
         Log.d(TAG, "Initialization failed with exception: " + p0?.message)
       }
     })
    

After you've successfully done that, let's jump straight into code and explain what we're going to be doing. We need to:

  • Log the user in
  • Get the group list
  • Join a group
  • Participate in the chat

On the Login screen, you need to provide the UID (User ID) to log in. There are five premade users which we can use for development. CometChat's predefined users have following IDs: SUPERHERO1, SUPERHERO2, SUPERHERO3, SUPERHERO4 and SUPERHERO5. We can use any of these UIDs to login.

The method that gets called is attemptLogin which looks like this:

private fun attemptLogin() {
    val UID = usernameEditText.text.toString()
    CometChat.login(UID, GeneralConstants.API_KEY, object : CometChat.CallbackListener<User>() {
        override fun onSuccess(user: User?) {
            redirectToMainScreen()
        }

        override fun onError(p0: CometChatException?) {
            Toast.makeText([email protected], p0?.message, Toast.LENGTH_SHORT).show()
        }
    })
}

After successfully logging the user in, you get the User object back. You don't have to save the logged in user manually, CometChat does it for you, and you can always access the user by using CometChat.getLoggedInUser() method. In onSuccess we simply redirectToMainScreen which does what it's name suggests.

Then the next thing to do is to fetch the list of groups. There's already a predefined group that all users belong to, called "Comic Heroes' Hangout". Again, with CometChat API, we get the groups like this:

private fun refreshGroupList() {
    // Get all the groups visible to this user
    // Since we're working with public groups only, all users should see all of them
    var groupRequest: GroupsRequest? = GroupsRequest.GroupsRequestBuilder().build()

    groupRequest?.fetchNext(object : CometChat.CallbackListener<List<Group>>() {
        override fun onSuccess(p0: List<Group>?) {
            updateUI(p0)
        }

        override fun onError(p0: CometChatException?) {
            Toast.makeText([email protected], p0?.message, Toast.LENGTH_SHORT).show()
        }
    })
}

List of all groups

Upon getting the groups back, we can update the list of groups shown on the screen. The updateUI method is fairly simple - it just shows the newly fetched groups:

private fun updateUI(groups: List<Group>?) {
    val adapter = GroupsAdapter(groups, this)
    groupsRecyclerView.adapter = adapter
}

After that, you should be able to see at least that 1 group, and possibly more if you've created them.

The UI is pretty basic, but groups contain heaps more data to show if you needed.

In the list of groups, you can see that the ones you're a part of say JOINED and others do not. Since all the groups are public, everyone can join any group, no need for a password or any invitation or anything - just tap on a group and you'll join it. Upon joining, you can participate in the chat. The logic for determining whether to open group chat or try to join a group is outlined in these few lines of GroupsAdapter.kt:

holder.container.setOnClickListener {
    if (group.isJoined) {
        goToGroupScreen(group)
    } else {
        attemptJoinGroup(group)
    }
}

This is how attemptJoinGroup looks, and in it's calling updateJoinedStatus to display the JOINED label in the list:

private fun attemptJoinGroup(group: Group) {
    // Try to join the group
    CometChat.joinGroup(group.guid, group.groupType, group.password, object : CometChat.CallbackListener<Group>() {
        override fun onSuccess(p0: Group?) {
            updateJoinedStatus(group)
        }

        override fun onError(p0: CometChatException?) {
            Toast.makeText(context, p0?.message, Toast.LENGTH_SHORT).show()
        }
    })
}

private fun updateJoinedStatus(group: Group) {
    groups!!.forEach {
        // If successful, in order the show "JOINED" on that group, go through all of them, find the one we just joined,
        // And set Joined = true
        if (it.guid == group.guid) {
            it.setHasJoined(true)
            notifyDataSetChanged()
        }
    }
}

We finally come to the most crucial part - the actual chat screen. I've used a regular RecyclerView to display messages, and this portion of the docs to implement the actual messaging. With some easy callbacks, you can listen for incoming messages and display them as they come.

The code to send the message:

private fun attemptSendMessage() {
    // Attempts to send the message to the group by current user
    val text = messageEditText.text.toString()
    if (!TextUtils.isEmpty(text)) {
        messageEditText.text.clear()
        val receiverID: String = group!!.guid
        val messageType: String = CometChatConstants.MESSAGE_TYPE_TEXT
        val receiverType: String = CometChatConstants.RECEIVER_TYPE_GROUP

        val textMessage = TextMessage(receiverID, text, messageType, receiverType)

        CometChat.sendMessage(textMessage, object : CometChat.CallbackListener<TextMessage>() {
            override fun onSuccess(p0: TextMessage?) {
                addMessage(p0)
            }

            override fun onError(p0: CometChatException?) {

            }
        })
    }
}

addMessage method merely adds a new message at the end and scrolls to it:

private fun addMessage(message: TextMessage?) {
    messagesAdapter.addMessage(message)
    messagesRecyclerView.smoothScrollToPosition(messagesAdapter.itemCount - 1)
}

We also need to attach the listener responsible for getting incoming messages in onResume and remove it in onPause:

override fun onResume() {
    super.onResume()
    // Add the listener to listen for incoming messages in this screen
    CometChat.addMessageListener(listenerID,object :CometChat.MessageListener(){
        override fun onTextMessageReceived(message: TextMessage?) {
            addMessage(message)
        }
        override fun onMediaMessageReceived(message: MediaMessage?) {

        }
    })
}
override fun onPause() {
    super.onPause()
    CometChat.removeMessageListener(listenerID)
}

And finally, the class responsible for showing all the messages - the MessagesAdapter.kt. It is simply filling in some data from the TextMessage class, such as the sender's name and the actual content. It's also determining whether the message is from currentUser - that is, the user logged in on this device or not and changes the layout accordingly. That's done using two different ViewTypes in the RecyclerView like so:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
    if (viewType == GeneralConstants.MY_MESSAGE) {
        return MessageViewHolder(LayoutInflater.from(context).inflate(R.layout.my_message_layout, parent, false))
    }
    return MessageViewHolder(LayoutInflater.from(context).inflate(R.layout.others_message_layout, parent, false))
}

override fun getItemViewType(position: Int): Int {
    if (isCurrentUserMessage(messages[position])) {
        return GeneralConstants.MY_MESSAGE
    }
    return GeneralConstants.OTHERS_MESSAGE
}

private fun isCurrentUserMessage(message: TextMessage?): Boolean {
    val currentUserId = CometChat.getLoggedInUser()?.uid
    return currentUserId!! == message?.sender?.uid
}

We are inflating a different layout depending on whether the message came from us or someone else. The layouts are my_message_layout.xml and others_message_layout.xml respectively. Also, the onBindiewHolder method is pretty simple, it just fills out the content and uses UI Avatars to load the images into circular ImageViews with Glide:

override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
    holder.messageTextView.text = messages[position]?.text
    // Check if the sender is the current user
    Glide.with(context).load(GeneralConstants.AVATARS_URL + messages[position]?.sender?.name).into(holder.avatarImageView)
}

That's it, and now we're down to testing. To test the app properly, you should run it and log in as one of the test users. You can also launch two emulators side by side and login with two different users and play around with the chat. It should look something like this:

CometChat group chat in action

Conclusion

CometChat is both simple and powerful. With intuitive API and great docs, you can quickly master this library and make some astonishing chat applications. The options are truly abundant, and I feel like I've only scratched the surface with this demo. There's a lot I didn't cover:

  • private/password protected groups
  • proper sign up/log in
  • list of participants in the group and actions on them (kick/ban/unban/change user's scope)
  • other types of messages, not just text

What we did learn is how easy it can be to implement the chat in your application. Hopefully, some of these very interesting topics will be covered in some next tutorial, where we can dive a bit deeper into the power of CometChat.