Chat

Building an Android Chat Application with Kotlin

4 min read Syed Ahmed on May 8, 2019

Building a real-time Android chat app can be challenging. With tons of solutions, services, and open-source options available, choosing the right stack isn’t always clear. In this tutorial, we’ll use PubNub to power our Android chat app.

We’ll start with the basics, writing an Android chat application in Kotlin that can send (publish) and receive (subscribe) data between Android phones.

What is the publisher/subscriber model?

Analytics

To understand the publisher/subscriber pattern, imagine a classroom. In the classroom, think of the teacher as a publisher and the students as the subscribers. Whenever the teacher writes something on the board (publishes) all the students can see it.

Using PubNub, there are data conduits called channels. In our analogy, channels are classrooms. With PubNub, there is no limit of publishers and subscribers.

In our example, PubNub plays the role of the school. Facilitating the teachers (publishers) and students (subscribers) to be able to communicate. PubNub also bakes-in crucial functionality such as granting permissions and in-transit data manipulation, so you can make secure 1-to-1 chats.

The full Android Chat Example GitHub repository is available as well.

Configuring and Creating the Android Project

To start off, we’ll need to make sure that we have Android Studio configured. To get it set up, download Android Studio. Once that’s done, run it and go through the one-time setup.

PubNub-Kotlin

Great, now that we have Android Studio, create a new project. To build this project in Kotlin, we need to make sure that we check the Kotlin checkbox during the setup. For most of the setup, clicking “Next” on all of the default settings is fine.

The next step is adding PubNub to our project. The best way to do that is to add the dependency in the app level Gradle file of your project.

dependencies {
    implementation group: 'com.pubnub', name: 'pubnub-gson', version: '4.20.0'
    // … Other code
}

Creating a Frontend in XML for the Android app

We’ll make the front-end show both sides of the publisher-subscriber pattern. We’ll add a TextView that will show the latest message published. Also, we’ll add a TextEdit that will allow the user to input and publish a message.

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/textViewSubscribe"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="Messages received will be displayed here"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <EditText
        android:id="@+id/editTextPublish"
        android:layout_width="wrap_content"
        android:layout_height="45dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:hint="Enter message to send"
        android:inputType="text|textPersonName"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />
</android.support.constraint.ConstraintLayout>

NOTE: You must ask for user permission for the application to run properly at runtime. The line of code below must be included in AndroidManifest.xml.

<uses-permission android:name="android.permission.INTERNET" />

 

Publishing and Subscribing in an Android App

At this point, we’ll head over to PubNub and signup for a free account.

This allows us to send and receive chat app messages with our own API keys. Once we’ve signed up, we can edit our MainActivity.kt file, specifically the OnCreate function, to add our logic.

First, import these libraries and dependencies to the top of your file:

import com.pubnub.api.PNConfiguration
import com.pubnub.api.PubNub
import com.pubnub.api.callbacks.PNCallback
import com.pubnub.api.models.consumer.PNPublishResult
import com.pubnub.api.models.consumer.PNStatus
import com.pubnub.api.callbacks.SubscribeCallback
import com.pubnub.api.models.consumer.pubsub.PNMessageResult
import com.pubnub.api.models.consumer.pubsub.PNPresenceEventResult
import android.widget.EditText
import android.widget.TextView
import android.view.View
import android.view.KeyEvent
import java.util.Arrays

Then we’re going to configure our PubNub object with your PubNub publish/subscribe keys.

val pnConfiguration = PNConfiguration()
pnConfiguration.subscribeKey = "ENTER_YOUR_PUBNUB_SUBSCRIBE_KEY"
pnConfiguration.publishKey = "ENTER_YOUR_PUBNUB_PUBLISH_KEY"
val pubNub = PubNub(pnConfiguration)

After configuring PubNub, we can add front-end components in our MainActivity.kt file. We’ll configure an event handler for when the user hits the enter button on their keyboard. This will tell our app that the user wants to send their message, and we can publish their text with PubNub.

val publishText = findViewById<EditText>(R.id.editTextPublish)
val subscribeText = findViewById<TextView>(R.id.textViewSubscribe)
publishText.setOnKeyListener(View.OnKeyListener { v, keyCode, event ->
            if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) {
                pubNub.run {
                    publish()
                            .message(publishText.text.toString())
                            .channel("whiteboard")
                            .async(object : PNCallback<PNPublishResult>() {
                                override fun onResponse(result: PNPublishResult, status: PNStatus) {
                                    if (!status.isError) {
                                        println("Message was published")
                                    }else {
                                        println("Could not publish")
                                    }
                                }
                            })
                }
                return@OnKeyListener true
            }
            false
        })

Great! The next step is to listen for incoming messages. We’ll do that by subscribing to the same channel that we’re publishing to. We’ll also be adding a listener which will take a callback as a parameter. We’ll override the SubscribeCallback object available in the PubNub library.

var subscribeCallback: SubscribeCallback = object : SubscribeCallback()  {
            override fun status(pubnub: PubNub, status: PNStatus) {
            }
            override fun message(pubnub: PubNub, message: PNMessageResult) {
                runOnUiThread {
                    subscribeText.text = message.message.toString()
                }
            }
            override fun presence(pubnub: PubNub, presence: PNPresenceEventResult) {
            }
}
pubNub.run {
            addListener(subscribeCallback)
            subscribe()
                    .channels(Arrays.asList("whiteboard")) // subscribe to channels
                    .execute()
}

In the end, our OnCreate function should look similar to this.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val publishText = findViewById<EditText>(R.id.editTextPublish)
        val subscribeText = findViewById<TextView>(R.id.textViewSubscribe)
        var subscribeCallback: SubscribeCallback = object : SubscribeCallback()  {
            override fun status(pubnub: PubNub, status: PNStatus) {
            }
            override fun message(pubnub: PubNub, message: PNMessageResult) {
                runOnUiThread {
                    subscribeText.text = "${subscribeText.text} 
${message.message.toString()}" } } override fun presence(pubnub: PubNub, presence: PNPresenceEventResult) { } } val pnConfiguration = PNConfiguration() pnConfiguration.subscribeKey = "ENTER_YOUR_PUBNUB_SUBSCRIBE_KEY" pnConfiguration.publishKey = "ENTER_YOUR_PUBNUB_PUBLISH_KEY" val pubNub = PubNub(pnConfiguration) pubNub.run { addListener(subscribeCallback) subscribe() .channels(Arrays.asList("Chat")) // subscribe to channels .execute() } publishText.setOnKeyListener(View.OnKeyListener { v, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) { pubNub.run { publish() .message(publishText.text.toString()) .channel("Chat") .async(object : PNCallback<PNPublishResult>() { override fun onResponse(result: PNPublishResult, status: PNStatus) { if (!status.isError) { println("Message was published") }else { println("Could not publish") } } }) } return@OnKeyListener true } false }) }

And that’s it! We have built a simple Android application using Kotlin.

0