Build a modern Android chat app with Kotlin

Arth Limchiu

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_HERE 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([email protected], "Login successful", Toast.LENGTH_SHORT).show()
            }

            override fun onError(e: CometChatException) {
                Toast.makeText([email protected], "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. 😎

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([email protected], 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?! 😉

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 🎉

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 🎉! 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.

CometChat Pro iOS Documentation

CometChat Pro Javascript Documentation

WRITTEN BY

Arth Limchiu

Arth is an Android developer based in Philippines. He focuses on using Kotlin to build Android apps. He writes to help aspiring Android developers to learn more about Android development. When he's not coding, he watches Dota 2 streams.

CometChat