facebook How to Build an Android Chat App with Kotlin using QuickBlox SDK • QuickBlox

How to Build an Android Chat App with Kotlin using QuickBlox SDK

Egor Pavlov
1 Dec 2021
Kotlin Chat App

Note: This blog has been updated since it was originally published in May 2020

Kotlin is a relatively young language, but it is quickly gaining popularity among developers due to its convenient and modern approach to programming Android apps. In fact, in May 2019, Google announced that Kotlin was now its preferred programming language for Android app development.

Keeping pace with current trends, the team at QuickBlox have developed a feature-rich chat SDK for Android suitable for applications written in Kotlin (or Java). Our step-by-step chat SDK Android tutorial below will show you how to add stunning real-time communication features to your modern Android messaging app powered by QuickBlox.

Let’s go!

Getting Started

Registering for an account

To start off with the QuickBlox SDK, you need to go through a simple registration following this link.

Create a New Application in the Admin Panel

Now that you have an account with QuickBlox, you can proceed to the next preliminary step.

Create an application in the QuickBlox admin panel. QuickBlox application is a separate space to store your data, users, dialogs, future chat histories, custom objects, and more. To do this, simply click the “+” button.

Add a new app

The next step is to enter a brief description of the chat app:

Admin panel

As you can see on this screen, Title and Type are two required fields.

Title is the name of the application that will be displayed in the QuickBlox admin panel.

Type is the type of your application.

Please note that there can be more than one application inside your admin panel. It is recommended that you create a new QuickBlox application for every new project.

Click the “Add” button at the bottom of the page and the browser will redirect you to the screen with the application already created.

Accessing Credentials In the Admin Panel of the Application

At this point, we should see the following:

Dashboard

Next we will need the Application ID, Authorization key, Authorization secret, and below – the Account key. All these are required for the following authentication of the client-side application using QuickBlox API.

1. Connecting Quickblox Android SDK

To connect the QuickBlox chat messaging SDK for Android to your app, import QuickBlox SDK dependencies via “build.gradle” file (Module: app):

dependencies {
   implementation "com.quickblox:quickblox-android-sdk-messages:3.9.2"
   implementation "com.quickblox:quickblox-android-sdk-chat:3.9.2"
   implementation "com.quickblox:quickblox-android-sdk-content:3.9.2"
 }

The next step is to synchronize the project with Gradle files. To do this, click the appropriate button inside Android Studio – “Sync Project with Gradle Files”.

After successful synchronization of the project, the previously added dependencies will allow you to interact with the Quickblox Android SDK.

2. Quickblox Android SDK Initialization in the Application

First, we start by creating a new class which extends android.app.Application as follows:

import android.app.Application
import com.quickblox.auth.session.QBSettings

//App credentials
private const val APPLICATION_ID = ""
private const val AUTH_KEY = ""
private const val AUTH_SECRET = ""
private const val ACCOUNT_KEY = ""

private const val API_ENDPOINT = "https://your.api.endpoint.com"
private const val CHAT_ENDPOINT = "your.chat.endpoint.com"  //Attention!   Without   https://

class AppTest : Application() {

   override fun onCreate() {
       super.onCreate()
       checkAppCredentials()
       initQuickBloxSDK()
   }

   private fun checkAppCredentials() {
       if (APPLICATION_ID.isEmpty() || AUTH_KEY.isEmpty() || AUTH_SECRET.isEmpty() || ACCOUNT_KEY.isEmpty()) {
           throw AssertionError("Something wrong with credentials")
       }
   }

   private fun initQuickBloxSDK() {
       QBSettings.getInstance().init(applicationContext, APPLICATION_ID, AUTH_KEY, AUTH_SECRET)
       QBSettings.getInstance().accountKey = ACCOUNT_KEY

       // QBSettings.getInstance().setEndpoints(API_ENDPOINT, CHAT_ENDPOINT, ServiceZone.PRODUCTION)
       // QBSettings.getInstance().zone = ServiceZone.PRODUCTION
   }
}

Note that this class must be registered in AndroidManifest.xml. Specify the android:name property in the the node in AndroidManifest.xml:

<application
   android:name=".AppTest"
...
</application>

Also, since we have just signed up, we do not have our own Chat and API servers (these are only available on the Enterprise plan), so there is no need to call the method QBSettings.getInstance().setEndpoints() and QBSettings.getInstance().zone = The Chat and API Domain values obtained will automatically be set.

We have created a library for this purpose, which contains claim type constants (APPLICATION_ID, AUTH_KEY, ...) to use them both in the server and the client projects. Please note that these are your Application ID, Authorization key, Authorization secret and Account key that you can find in the newly created application.

Please be careful, while copying and pasting them into the value of the corresponding constants.

Note! The QBSettings.getInstance().init method must be called before we initialize Activities or Fragments. Which is why we had to create a class file that extends android.app.Application.

After the initialization, a user authorization procedure can be performed.

3. User Authorization

Let’s say we have created a screen, which we correspondingly called LoginActivity, where the application user can input, for example, username, password, and then click the “Login” button to enter the application:

<EditText
   android:id="@+id/et_login"
   android:layout_width="match_parent"
   android:layout_height="44dp"
   android:layout_marginLeft="16dp"
   android:layout_marginTop="11dp"
   android:layout_marginRight="16dp"
   android:paddingLeft="12dp"
   android:paddingRight="12dp"
   android:singleLine="true"
   android:hint="Enter Your Login" />

<EditText
   android:id="@+id/et_password"
   android:layout_width="match_parent"
   android:layout_height="44dp"
   android:layout_marginLeft="16dp"
   android:layout_marginTop="11dp"
   android:layout_marginRight="16dp"
   android:paddingLeft="12dp"
   android:paddingRight="12dp"
   android:singleLine="true"
   android:hint="Enter Your Password" />

<Button
   android:id="@+id/btn_login"
   android:layout_width="match_parent"
   android:layout_height="44dp"
   android:layout_marginLeft="16dp"
   android:layout_marginTop="42dp"
   android:layout_marginRight="16dp"
   android:text="Login"/>

To simplify* we apply user’s authorization with a username and password, but we can just enter an email + password.

*(Or even use the Firebase service for authorization and submit it to the server using the Firebase Project ID and Access Token.)

After the user inputs the username and password, we will need to build the QBUser model for the following authorization and call the method of the QuickBlox Android SDK: QBUsers.signIn(...):

private fun prepareUser() {
   val qbUser = QBUser()
   qbUser.login = loginEt.text.toString().trim { it <= ' ' }
   qbUser.password = passwordEt.text.toString().trim { it <= ' ' }

   signIn(qbUser)
}

private fun signIn(user: QBUser) {
   QBUsers.signIn(user).performAsync(object : QBEntityCallback {
       override fun onSuccess(qbUser: QBUser?, bundle: Bundle?) {
           // Successfully Signed In
           loginToChat(user)
       }

       override fun onError(e: QBResponseException?) {
           // Sign In Error
           if (e?.httpStatusCode == 401) {
               signUp(user)
           } else {
               Toast.makeText(this@LoginActivity, "Sign In Error: " + e?.message, Toast.LENGTH_LONG).show()
           }
       }
   })
}

If you correctly added the dependencies to the build.gradle file (application level), all the resources from the QuickBlox Android SDK will be available, as well as the QBUser model and the QBUsers.signIn(...) method that we just used. If the model is not available, it means the dependencies are not connected correctly, or the project is not synchronized.

The code section above shows that when, as a result of the QBUsers.signIn(...) method, the onSuccess callback is invoked, this means that we are successfully logged in the API (as such“user existed”) and now we can log in to the Chat server.

In case something went wrong, the onError callback will be invoked. Also, as it can be seen from the code section above – one of the reasons for this may be that this user has not yet been created and the API returns error 401.

Considering this we will create a new user using QBUsers.signUp(...):

private fun signUp(user: QBUser) {
   QBUsers.signUp(user).performAsync(object : QBEntityCallback {
       override fun onSuccess(qbUser: QBUser?, b: Bundle?) {
           // Successful Sign Up
           signIn(user)
       }

       override fun onError(e: QBResponseException?) {
           // Sign Up Error
           Toast.makeText(this@LoginActivity, "Sign Up Error: " + e?.message, Toast.LENGTH_LONG).show()
       }
   })
}

After creating the user (Sign Up), we must log in with the newly created user by calling signIn(...) method again.

To configure a connection to the Chat server is essential before connecting to it:

private fun setupChatConnection() {
  val configurationBuilder = QBChatService.ConfigurationBuilder()
  configurationBuilder.socketTimeout = 300
  configurationBuilder.isUseTls = true // TLS is disabled by default
  configurationBuilder.isKeepAlive = true
  configurationBuilder.isAutojoinEnabled = false
  configurationBuilder.setAutoMarkDelivered(true)
  configurationBuilder.isReconnectionAllowed = true
  configurationBuilder.setAllowListenNetwork(true)
  configurationBuilder.port = 5223

  QBChatService.setConfigurationBuilder(configurationBuilder)
}

Now that we are successfully logged in, let’s proceed to authorization to the Chat server:

private fun loginToChat(user: QBUser) {
   QBChatService.getInstance().login(user, object : QBEntityCallback {
       override fun onSuccess(v: Void?, b: Bundle?) {
           // Chat Login Successful
       }

       override fun onError(e: QBResponseException?) {
           // Chat Login Error
       }
   })
}

After successful authorization to the Chat server, we can interact with the API and Chat server.

4. Message List

Before sending messages, you need to download the already existing dialogs: in which our user is listed among occupants:

private fun loadDialogsFromServer() {
   val requestBuilder = QBRequestGetBuilder()
   requestBuilder.limit = 100
   QBRestChatService.getChatDialogs(null, requestBuilder).performAsync(object : QBEntityCallback> {
       override fun onSuccess(dialogs: ArrayList?, b: Bundle?) {
           // Dialogs Loaded Successfully
       }

       override fun onError(e: QBResponseException?) {
           // Loading Dialogs Error
       }
   })
}

When dialogs are downloaded successfully, we have got the ArrayList collection, which may be empty if the user doesn’t participate in at least one dialog.

You can save the downloaded dialog collection in any convenient and familiar way, and update as necessary. You will need it when a new dialog is created with the user, or the user was invited to an existing dialog.

It can be saved in an internal database, or in a cache, or otherwise.

We can now create a dialog with any user that exists in the current QuickBlox application, but to do this you need to download at least several users first.

5. Getting Users from Dialogs

The next thing you can do is download and save users who already have dialogs. This must be done in order to get user models from the server and update information about them in our internal storage– because someone could have changed their name, phone number, avatar, or other fields since the last update.

To download the models of specific users from the server, we can take the dialogs list, get participants from each dialog, create a list from the IDs of these users, and download them from the server. This can be done in the following way:

private fun prepareUsersIDsToLoad(dialogs: ArrayList) {
   val userIDs = HashSet()
   for (dialog in dialogs) {
       userIDs.addAll(dialog.occupants)
   }

   val requestBuilder = QBPagedRequestBuilder(100, 1)
   loadUsersFromDialogs(userIDs, requestBuilder)
}

private fun loadUsersFromDialogs(userIDs: HashSet, requestBuilder: QBPagedRequestBuilder) {
   QBUsers.getUsersByIDs(userIDs, requestBuilder).performAsync(object : QBEntityCallback> {
       override fun onSuccess(qbUsers: ArrayList?, bundle: Bundle?) {
           if (qbUsers != null) {
               bundle?.let {
                   val totalPages = it.get("total_pages") as Int
                   val currentPage = it.get("current_page") as Int
                   if (totalPages > currentPage) {
                       requestBuilder.page = currentPage + 1
                       loadUsersFromDialogs(userIDs, requestBuilder)
                   } else {
                       // All Users From Dialogs Loaded
                   }
               }
           }
       }

       override fun onError(e: QBResponseException?) {
           // Loading Users Error
       }
   })
}

Here we have initiated a list of unique values from the user IDs that are occupants in each dialog available to us. Then we have created a QBPagedRequestBuilder with the parameters Per page = 100 and page = 1.

Next, when calling the function onSuccess callback (successful download), we check whether we have downloaded all the users, or if we repeat the request by increasing the “page number” by 1.

For example, if we have 146 unique users from the dialogs, our method will work twice. The first time it will load 100 users, then it will call itself again and load the remaining 46.

6. Getting Users by a Specific Parameter

Now let’s download more users from the server, for example, it will be the 100 recently created users from our QuickBlox application:

private fun loadUsersFromQuickBlox() {
   val rules = ArrayList()
   rules.add(GenericQueryRule("order", "desc string created_at"))

   val requestBuilder = QBPagedRequestBuilder()
   requestBuilder.rules = rules
   requestBuilder.perPage = 100
   requestBuilder.page = 1
  
   QBUsers.getUsers(requestBuilder).performAsync(object : QBEntityCallback> {
       override fun onSuccess(usersFromServer: ArrayList?, b: Bundle?) {
          
       }

       override fun onError(e: QBResponseException?) {
          
       }
   })
}

Please note that in order to create a dialog, you must have at least two users in your QuickBlox application. Users can be created in the admin panel, or by installing the application on several devices, after the registration of a new user (Sign Up).

If QBUsers.getUsers(...) method is performed correctly, the onSuccess() callback will be invoked, which indicates the successful loading of the collection ArrayList. Along with the Dialogs list, you can store it in any convenient and familiar way, and update as necessary.

7. Creating a Dialog

Now that we have some users, we can create a dialog with them.

Before we do this, let’s remember that there are three types of dialogs:

  1. Private (QBDialogType.PRIVATE) (contains only two users, communicating one-on-one and there is no way to add anyone else to this dialog).
  2. Group (QBDialogType.GROUP) (any number of users can participate in the dialog, usually 3 or more. We can also create a group dialog for two users, assuming that we want to add more participants to it).
  3. Public (QBDialogType.PUBLIC_GROUP) (All users from your application will be able to join it. The server will create a public chat and return detailed information about the newly created dialog).

Important! The public dialog model does not contain a list of user IDs. Therefore, publicDialog.occupants doesn’t have a value.

Note. You can also create dialogs in the QuickBlox admin panel.

Let’s create a group dialog with any users from the list we have downloaded earlier:

private fun createDialog(usersIdsList: ArrayList) {
   val newDialog = QBChatDialog()

   newDialog.name = "My awesome chat"
   newDialog.type = QBDialogType.GROUP
   newDialog.setOccupantsIds(usersIdsList)

   QBRestChatService.createChatDialog(newDialog).performAsync(object : QBEntityCallback {
       override fun onSuccess(qbChatDialog: QBChatDialog?, b: Bundle?) {
           qbChatDialog.join(DiscussionHistory())
          
       }

       override fun onError(e: QBResponseException?) {
          
       }
   })
}

At the beginning we created usersIdsList: ArrayList. It’s a collection of user IDs with whom we want to create a new dialog. We can create this list, for example, in the following way:

private fun makeUsersIDsCollection(usersToCreateDialog: ArrayList): ArrayList {
   val usersIdsList = ArrayList()
  
   for (qbUser : QBUser in usersToCreateDialog) {
       usersIdsList.add(qbUser.id)
   }

   return usersIdsList
}

Here usersToCreateDialog is already the list of users with whom we want to create a dialog.

As a result, when onSuccess() callback is triggered, we get the QBChatDialog model of the dialog we just created. It can be added to those already stored, or again request the entire list of dialogs from the server, as we did earlier (QBRestChatService.getChatDialogs(...)).

Note! After receiving a callback about the successful creation of the dialog (onSuccess), we join the dialog for sending and receiving messages using: qbChatDialog.join(DiscussionHistory()). We can join only to a Group dialog and not a Private dialog.

Now we are ready to send the first message to the new dialog.

8. Sending a message

Let’s compose a message and send it to the dialog:

private fun sendMessage(qbChatDialog: QBChatDialog, message: String) {
   val qbChatMessage = QBChatMessage()
   qbChatMessage.body = message

   qbChatMessage.setSaveToHistory(true)  // To be able to retrieve this message in Dialog’s chat history
   qbChatMessage.dateSent = System.currentTimeMillis() / 1000
   qbChatMessage.isMarkable = true      // To be able to mark message as delivered or as read further 

   qbChatDialog.sendMessage(qbChatMessage, object : QBEntityCallback {
       override fun onSuccess(v: Void?, b: Bundle?) {
           // Message sent
       }

       override fun onError(e: QBResponseException?) {
           // Sending Error
       }
   })
}

Now, if we download the message history of this dialog, our message will appear in it.

9. Loading Chat History

To download the chat history, you should specify the section of messages downloaded from the server (limit), and the number of messages that should be skipped (skip), in case we want the next section of messages.

private fun loadChatHistory(qbChatDialog: QBChatDialog) {
   val messageGetBuilder = QBMessageGetBuilder()
   messageGetBuilder.limit = 50
   messageGetBuilder.skip = 0      // To load first 50 messages
   messageGetBuilder.sortDesc("date_sent")    // To retrieve date-send-sorted Collection
   messageGetBuilder.markAsRead(false)    // To mark each message as Read
  
   QBRestChatService.getDialogMessages(qbChatDialog, messageGetBuilder).performAsync(object : QBEntityCallback> {
       override fun onSuccess(messages: ArrayList?, b: Bundle?) {
          
       }

       override fun onError(e: QBResponseException?) {
          
       }
   })
}

As a result of a successful request to the server, onSuccess() callback works and we get a messages collection: messages: ArrayList.

Just as in the case of downloading Dialogs and Users (QBChatDialog, QBUser), messages (QBChatMessage) can be stored and updated in any way convenient for you.

Remember when we created the message, we set a parameter “qbChatMessage.setSaveToHistory(true)”?

If the value of this parameter is false, then the message will not be saved on the server, and, accordingly, will not appear in the chat history downloaded from the server.

In the same way, we can load chat history from any dialog available to us.

10. Dialog Message Listener

Almost all the main functionality of our application is now described. Let’s see how to listen to the incoming messages.

All we need is to add a listener (QBChatDialogMessageListener) to the Dialog or listen to all messages, not just from a specific one.

Let’s listen to only one dialog:

qbChatDialog.addMessageListener(object : QBChatDialogMessageListener {
   override fun processMessage(dialogId: String?, qbChatMessage: QBChatMessage?, senderId: Int?) {
      // Message (qbChatMessage) received from Dialog with ID = dialogId ; From user with ID = senderID
   }

   override fun processError(dialogId: String?, e: QBChatException?, qbChatMessage: QBChatMessage?, senderId: Int?) {
      
   }
})

We can see this listener has two callbacks: processMessage and processError. In this case, we need to process events from processMessage. These events will be incoming messages, we will get a QBChatMessage model including all the values of the model fields set by the sender.

– Let’s listen to all the dialogs:

We can use the same listener without a reference to a particular dialog. This is required when we need to receive incoming messages from all dialogs of the user.

private fun registerAllDialogsMessageListener() {
   val incomingMessagesManager = QBChatService.getInstance().incomingMessagesManager

   incomingMessagesManager.addDialogMessageListener(object : QBChatDialogMessageListener {
       override fun processMessage(dialogId: String?, qbChatMessage: QBChatMessage?, senderId: Int?) {
           // New Message Received
       }

       override fun processError(dialogId: String?, e: QBChatException?, qbChatMessage: QBChatMessage?, senderId: Int?) {
           // Error Message Received
       }
   })
}

11. Messages in the Background

Along with all the other functionality, we need to disconnect from the Chat server when the user sends the application to the background (by minimizing it or by switching to another application), and reconnect again when necessary. For example, the user might want to add an attachment to a chat message, but when they open file explorer to pick a file, the chat application goes into the background and disconnects from the chat server. And when they return again to the application with the picked file, the app needs to be reconnected to the chat.

As soon as we disconnect from the Chat server, the server understands that we have gone “offline”, and it is necessary to interact with the user in a different way.

Push notifications notify the user about any event, whether it’s an incoming voice or video call, new message or alert, advertising promotion, a new chat dialog, a friend request for a social network, and so on. Notifications can navigate users to a specific chat or destination. The main advantage of Push notifications is that they will be received by the device even if our application is not currently running.

Push notifications are worth a separate article. See our previous blogs on this topic: What are push notifications? and How push-notifications can improve your business. For the scope of this tutorial the most important thing to know is that the Chat server, realizing that we are “offline,” for example, will monitor all messages in Private dialogs and as soon as we appear online (we launch the application, connect to the chat server and become “online” for it), the Chat server will consistently deliver to us messages that were addressed to us during our absence.

To disconnect from the chat server:

QBChatService.getInstance().logout(object : QBEntityCallback {
    override fun onSuccess(aVoid: Void?, bundle: Bundle?) {
        QBChatService.getInstance().destroy()
    }

    override fun onError(e: QBResponseException?) {

    }
})

And to connect to the Chat server again, as I mentioned above, you need to call (QBChatService.getInstance().login(...)).

12. End a User Session – Logout

Now let’s say we need to sign in as another user, or just log out of the current user’s account in the application.

private fun logout() {
   QBUsers.signOut().performAsync(object : QBEntityCallback {
       override fun onSuccess(v: Void?, b: Bundle?) {
           QBChatService.getInstance().destroy()
           // Start Login Screen
           finish()
       }

       override fun onError(e: QBResponseException?) {
           // Logout Error
       }
   })
}

As you can see, after a successful Logout from the API (QBUsers.signOut()), we need to log out from the Chat server, and at the same time clear everything that was connected with the current user session on the Chat server (QBChatService.getInstance().destroy()).

13. Conclusion

By following the steps in this tutorial you will be able to build an Android chat application written in Kotlin. Let’s provide a summary of these key steps so that you are clear about the logic sequence of the completed chat app:

  1. Initialize the QuickBlox SDK.
  2. Give the user a possibility to enter username and password (or email and password).
  3. Authorize the user in the API server (create a new one, or sign in as existing).
  4. Configure connection and authorization to the Chat server.
  5. Initialize the listener for incoming messages. QBChatDialogMessageListener in order to not miss messages.
  6. Download and save to temporary storage the dialogs in which this user participates.
  7. Collect user IDs from existing dialogs (if any) and load users that we “already know”.
  8. If you decide to create a dialog with users that are not yet among the already downloaded ones, load an additional 100 of the last created users, as an example.
  9. Create a new group dialog.
  10. Compose a message and send it to the previously created dialog.
  11. Download the chat history in this dialog, or in any other (if we have a screen displaying the chat history in the dialog, then we need to download the latest chat history every time a user wants to enter the dialog and view his history, or write a new message).
  12. Disconnect from the Chat server when minimizing the application (going to background), and connect to the Chat server when going to foreground.
  13. Logout.

The QuickBlox Android SDK has additional features available to support more advanced use-cases such as audio & video calling, video conferencing, push notifications, content moderation, rich messages, and more.

Check out our Kotlin chat sample and detailed Android documentation so that you can start building your Android chat app today.

For technical queries about using our SDK or recommendations for our product or documentation please submit a ticket to our support team.

Contact us if you want to learn more about how QuickBlox can support your communication needs.

Share article

Subscribe for news

Get the latest posts and read anywhere.


    Thanks for subscribing!

    You will receive an email shortly to verify your subscription.

    Check out your inbox!

    Ready to get started?

    QUICKBLOX
    QuickBlox post-box
    QuickBlox x

    Subscribe for news

    Get the latest posts and read anywhere.


      Don’t forget to visit our social networks:

      • QuickBlox twitter
      • QuickBlox fb
      • QuickBlox linkedin
      • QuickBlox medium
      • QuickBlox git
      • QuickBlox instagram