Tutorial: Build an Android chat app with PubNub

Learn how to build a scalable chat app using the PubNub Android SDK getting started tutorial.

Step 01

Build a Chat App with the PubNub Android SDK

This tutorial will walk you through developing a basic chat application in Kotlin using PubNub.  

This tutorial will focus on the Kotlin SDK and not the community-supported chat UI components designed for Kotlin.  

The source code for this application can be found on Github at https://github.com/PubNubDevelopers/GettingStarted-Kotlin-SDK-Tutorial

Prerequisites

Before running this application, be sure you have the following installed:

  • Android Studio.  The application was created with version ‘chipmunk’ but should work with any recent version of the IDE.

Download Source Application

In a terminal, navigate to an empty folder.  Enter the following command to clone the repository:

git clone https://github.com/PubNubDevelopers/GettingStarted-Kotlin-SDK-Tutorial cd GettingStarted-Kotlin-SDK-Tutorial

The main git branch contains the completed version of this tutorial.  If you would like to follow along, please checkout the following branch:

git checkout tutorial_step_0

Step 02

Create keys

Demo keys

You're ready to go!

We’ve assigned demo keys which are set up to support the features of this tutorial app. These will be active for 72 hours but you can sign up for a free account at the end of the tutorial to create your own permanent keys.

The demo keys have the following functionality activated ready for your application:

  • Presence

  • Message Persistence You will be able to see user’s message history and meta data.

  • Stream Controller You will be able to receive messages against a group of channels, or post one message to multiple channels.

  • App Context This allows you to manage the metadata of your users and channels.

My own keys

You'll need to create an account to use your own keys

  1. Sign in to your PubNub Dashboard.

  2. Go to Keysets using the left hand side navigation.

  3. Click the “Create New Keyset” button on the upper right hand-side of the portal.

  4. A pop-up appears. Give the keyset a name and click the “Create” button.

  5. Click on the newly created keyset.

  6. Enable four features for the keyset to be able to connect to the PubNub Network: Presence, Message Persistence, Stream Controller and App Context.

  7. Enable Presence by clicking on the toggle to turn it on. A pop-up will require that you enter in “ENABLE”. Enter in “ENABLE” in all caps and then press the “Enable” button. You can leave the default settings.

  8. Enable Message Persistence by clicking on the toggle to turn it on. You can leave the default settings.

  9. Enable Stream Controller by clicking on the toggle to turn it on if it is already not enabled. You can leave the default settings.

  10. Enable App Context by clicking on the toggle to turn it on if it is not already enabled. Select the region closest to you to store your context.

  11. Click on the “Save Changes” button in the bottom right corner of the portal. A confirmation message will appear in the upper-left corner of the portal confirming that these changes have been saved. 

  12. Scroll back to the top of the portal. Copy the Publish key by clicking on the copy icon and paste it below

  13. Copy the Subscribe Key by clicking on the copy icon and paste it below

Step 03

Build and run

Watch the video or follow the steps below to build and run your application.

JavaScript
//This code box will update soon as you
//progress through the video
//Use the copy icon in the top right to quickly copy
//code as you're following along

Step 1. Add PubNub Keys

Open the gradle.properties file and enter the publish and subscribe keys at the relevant location, found towards the bottom of the file

kotlin
#PubNub Keys
PUBNUB_PUBLISH_KEY = "REPLACE WITH YOUR PUBNUB PUBLISH KEY"
PUBNUB_SUBSCRIBE_KEY = "REPLACE WITH YOUR PUBNUB SUBSCRIBE KEY"

You will need to perform a Gradle sync to have these settings apply.

Step 2. Update the Android Manifest

Add two uses-permission tags to your Android manifest, just above the <application/> tag:

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

These are both required for PubNub to work on your Android device or emulator

Step 3. Adding chat to your application with PubNub

The application on the main branch is fully functional and contains all the changes described in the following steps.

If you want to follow along with the tutorial and build the functionality into a templated version of the app, please checkout the following branch:

shell
git checkout tutorial_step_0

Step 3a. Creating the PubNub object

Before interacting with PubNub, you first need to create a PubNub configuration instance.  This requires 3 things, the publish and subscribe keys that were created in the previous step and a device identifier.

The device identifier you pass in should represent a unique instance of the user or device that connects to PubNub.  If you maintain your own user registration workflow then you could use whichever unique identifier you assigned the user during registration.  If your users do not already have an assigned ID, as in the case of this app, you should assign a device-specific identifier. 

Android exposes several IDs that can be used for this purpose such as the Firebase installation ID, a privately generated GUID, or a user-resettable identifier such as Android_ID

kotlin
deviceId = Settings.Secure.getString(this.contentResolver, Settings.Secure.ANDROID_ID)

val config = PNConfiguration(UserId(deviceId)).apply {
  publishKey = BuildConfig.PUBLISH_KEY
  subscribeKey = BuildConfig.SUBSCRIBE_KEY
}
pubnub = PubNub(config)

Step 3b. Subscribing to a channel when in the foreground

The concepts of publish and subscribe are fundamental to PubNub where messages are sent from a publishing source and received by subscribers over a channel.  You do not need to create the channel separately, it will be created automatically for you by PubNub whenever the first user publishes or subscribes to it.

Despite the apparent simplicity, the Pub/Sub model is able to accommodate the most complex of communication use cases.  

To keep this application simple we will only have a single channel to represent the application’s single "chat room".  The application will subscribe to receive messages from the channel that have been published by other users.

Since you cannot rely on your application being active in the background on Android most chat developers will take a two stage approach:

  1. Subscribe to receive messages when the app comes to the foreground

  2. Unsubscribe when the app goes into the background

  3. Receive a mobile push notification whilst in the background to notify the user that a message is available.  This is outside of the scope of this tutorial but please see our Push Notifications tutorial for more information.

Since we want to receive presence events when users join or leave the channel, specify ‘withPresence’ to be true.

Add this code to the onResume() function:

kotlin
pubnub.subscribe(
  channels = listOf(groupChatChannel),
  withPresence = true
  )

Add this code to the onPause() function:

kotlin
pubnub.unsubscribe(
  channels = listOf(groupChatChannel)
  )

Step 3c. Publishing messages to a channel

Messages published to a channel will be received by all subscribers on that channel. 

The message payload is a String but you can also optionally add custom ‘meta’ information to the message as a JSON object.  This application only uses a data payload.

kotlin
pubnub.publish(
  channel = groupChatChannel,
  message = message
  ).async { result, status ->
    if (!status.error) {
    // Handle any errors
    }
  }

Step 3d. Setting up a PubNub listener

PubNub event listeners are used to receive:

  • Status information: A Pub/Sub call has completed

  • Presence information: A user has joined or left a channel.

  • Messages: A message has been received.

  • Signals: A signal has been received.

  • Message Reactions: Used to modify messages after they are published, for example adding an emoji reaction.

  • App Context: PubNub's serverless storage has been updated

Many of these listeners require the 'App Context' feature to be enabled on the keyset, which was done during the ‘Create Keys’ section of this tutorial.  

This tutorial only uses a subset of the possible listeners which will be populated in upcoming steps.

kotlin
val listener = object : SubscribeCallback() {
  override fun status(pubnub: PubNub, pnStatus: PNStatus) {
    //  Not used in this application
  }
  override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {
    //  todo       
  }
  override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
    //  todo
  }
  override fun objects(pubnub: PubNub, objectEvent: PNObjectEventResult) {
    //  todo
  }
}

Having created the listener object it must be added to the PubNub object, created in step 3a. Add the following code after the listener declaration.

kotlin
pubnub.addListener(listener)

Step 3e. Listening for new messages

Once an application subscribes to messages on a channel, the messages will be received by the message listener, created at step 3d.  

Populate the message override as follows:

kotlin
override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {
  if (pnMessageResult.channel == groupChatChannel) {
    //  Display the message by adding it to the viewModel
    val newMsg = Message()
    newMsg.message = pnMessageResult.message.asString
    newMsg.senderDeviceId = pnMessageResult.publisher.toString()
    newMsg.timestamp = pnMessageResult.timetoken!!
    chatViewModel.messages.add(newMsg)
  }
}

Step 3f. Listening for presence events

By subscribing to a channel’s presence events, as was done in step 3b, we will also receive events whenever a user joins or leaves the channel.

Modify the presence listener, created in step 3d, as follows to update the list of users present in the chat.

kotlin
override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
  when (pnPresenceEventResult.event) {
    "join" -> {
      addMember(pnPresenceEventResult.uuid.toString())
    }
    "leave" -> {
      removeMember(pnPresenceEventResult.uuid.toString())
    }
    "interval" -> {
      //  'join' and 'leave' will work up to the ANNOUNCE_MAX setting (defaults to 20 users)
      //  Over ANNOUNCE_MAX, an 'interval' message is sent. 
      //  The below logic requires that 'Presence Deltas' be defined for the keyset, explained later
      pnPresenceEventResult.join?.forEach { userId ->
        addMember(userId)
      }
      pnPresenceEventResult.leave?.forEach { userId ->
        removeMember(userId)
      }
    }
  }
}

When the application first starts, it needs to populate the list of users currently in the channel.  Since presence events only apply to users newly joining or leaving, the application calls the ‘hereNow’ function on launch to retrieve a list of device IDs already in the group chat. 

kotlin
pubnub.hereNow(
  channels = listOf(groupChatChannel),
  includeUUIDs = true
  ).async { 
    result, status ->
      if (!status.error) {
        //  The API will return an array of occupants in the channel, defined by their
        //  ID.  This application will need to look up the friendly named defined for
        //  each of these ID (later)
        result?.channels?.get(groupChatChannel)?.occupants?.forEach { i ->
          addMember(i.uuid)
        }
      } else {
        //  Handle error
      }
    }

Note that addMember() and removeMember() are responsible for updating the chat viewmodel and resolving UUIDs to friendly names. 

Step 3g. Retrieving past messages

When a user first joins the chat, it is often useful to provide some recently sent messages, to give some context of any conversation in progress.

Alternatively, if a user leaves and then rejoins the chat they will need to get up to speed on any messages they missed whilst they were way.

Both of these use cases are handled by the PubNub Message Persistence API.

In the onCreate function, invoke the history method to receive the past few messages

kotlin
pubnub.fetchMessages(
  channels = listOf(groupChatChannel),
  page = PNBoundedPage(limit = 8),
  includeUUID = true
  ).async { result, status ->
    if (!status.error) {
      result!!.channels.forEach { (channel, messages) ->
        if (channel == groupChatChannel) {
          messages.forEach { messageItem: PNFetchMessageItem ->
          val newMsg = Message()
          newMsg.message = messageItem.message.asString
          newMsg.timestamp = messageItem.timetoken
          newMsg.senderDeviceId = messageItem.uuid.toString()
          chatViewModel.messages.add(newMsg)
          lookupMemberName(newMsg.senderDeviceId)
        }
      }
    }
  } else {
    //  handle the error
  }
}

Step 3h. Assign a friendly name to the device and persist it with PubNub object storage

App Context is a very powerful serverless storage mechanism provided by PubNub and designed to meet many use cases of a standard chat app.  

For example, you can choose to use App Context to store:

  • User meta data such as name, avatar, email

  • Channel metadata such as name and description as well as who the members of that channel are

This application does not take full advantage of the App Context but does store a single piece of information, the ‘friendly name’ that the user can assign themselves. Assigning a friendly name avoids having to display the long user id in the member list.

Setting the ‘friendly name’ is handled in a separate activity, modify the ‘save’ button onClick() handler in SettingsUI.kt to write the name to the PubNub user meta data.

kotlin
pubnub.setUUIDMetadata(
  name = text.text
).async { result, status ->
  if (status.error) {
    //  Handle error
  } 
}

Step 3i. Lookup someone else's friendly name using PubNub Object storage

The PubNub App Context is considered the master copy of ‘friendly names’ set by anyone involved in the chat, as detailed in step 3h.

The presence listener, described in step 3f, only tells you the user ids of users joining or leaving the chat.  To update the list of member friendly names in the chat, add the following code to the lookupMemberName() function, in MainActivity

kotlin
pubnub.getUUIDMetadata(
  uuid = deviceId,
  ).async { result, status ->
    if (!status.error) {
      //  Add the user's name to the memberNames hashmap (part of the viewModel, so
      //  the UI will update accordingly)
      chatViewModel.memberNames.put(deviceId, result?.data?.name.toString())
    } else {
      //  Handle error
  }
}

What about if another user changes their friendly name? To be notified when the friendly name changes, add an "objects" override to the PubNub listener you created in step 3d, this objects listener will fire whenever any user meta data is updated.

kotlin
override fun objects(pubnub: PubNub, objectEvent: PNObjectEventResult) {
  if (objectEvent.extractedMessage.type.equals("uuid")) {
    val extractedMessage: PNSetUUIDMetadataEventMessage =
      objectEvent.extractedMessage as PNSetUUIDMetadataEventMessage
    replaceMemberName(
      extractedMessage.data.id,
      extractedMessage.data.name.toString()
     )
  }
}

One additional point about the ‘objects’ listener: You must have “User Metadata Events” applied to your keyset, as below.

Notes and other caveats

For efficiency, PubNub handles presence slightly differently depending on how many occupants you have in the channel.  If using this app with a number of users above the ‘Announce Max’ setting, which defaults to 20, be sure to enable the ‘Presence Deltas’ option in your admin dashboard.  This causes PubNub to report the user IDs who have joined or left as part of the interval event, which this app uses to maintain who is in the channel. 

Step 04

Congratulations - what's next?

Chat SDK Documentation

Chat OverviewKotlin SDK DocsChat Developer Path

Live Tour

Start the live coding tour

Developing to a tight deadline? Explore Chat Components

Chat Components IntroductionChat Components Documentation (Android)

Ready to get beyond Proof of Concept?

Schedule a 15 minute chat with one of our experts to get your project moving

Talk to an Expert
Keep building

Take it to production on your own keys

Create a free PubNub account to get permanent keys for this app.