Chat using PubSub - Android
room.pubSub sends messages between participants on named topics. This guide builds group and private chat on it. If you are new to PubSub, read the PubSub guide first.
publish(), subscribe() and unsubscribe() are suspend functions, so call them from a coroutine. They all require a joined room, and throw a VideoSDKException if the room has not been joined yet or is RECONNECTING. The examples below open the chat screen after the room is joined, so the room is already connected — if instead you subscribe as part of your join flow, do it from onRoomJoined().
Implementing Chat
Group Chat
- Choose the topic every participant publishes and subscribes to for chat messages. This guide uses
CHAT. - On the send button, publish the message that the sender typed in the
EditTextfield.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import live.videosdk.rtc.android.Room
import live.videosdk.rtc.android.lib.VideoSDKException
import live.videosdk.rtc.android.listeners.PubSubMessageListener
import live.videosdk.rtc.android.model.PubSubPublishOptions
import android.text.TextUtils
class ChatActivity : AppCompatActivity() {
private lateinit var etmessage: EditText
// Room
var room: Room? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
etmessage = findViewById(R.id.etMessage)
/**
* Here, we have created 'MainApplication' class, which extends android.app.Application class.
* It has a Room property.
* In your android manifest, you must declare the class implementing android.app.Application
* (add the android:name=".MainApplication" attribute to the existing application tag):
* In MainActivity.kt, we have set the Room property.
*
* For Example: (MainActivity.kt)
* val room = VideoSDK.createRoom(roomId = roomId, token = token, participantName = participantName)
* (this.application as MainApplication).room = room
*/
// Get Room
room = (this.application as MainApplication).room
findViewById<View>(R.id.btnSend).setOnClickListener { sendMessage() }
}
private fun sendMessage() {
// get message from EditText
val message: String = etmessage.text.toString()
if (!TextUtils.isEmpty(message)) {
val publishOptions = PubSubPublishOptions(persist = true)
// Sending the Message using the publish method
lifecycleScope.launch {
try {
room!!.pubSub.publish("CHAT", message, publishOptions)
// Clearing the message input
etmessage.setText("")
} catch (e: VideoSDKException) {
// Room is not joined, is reconnecting, or the message could not be sent
Toast.makeText(
this@ChatActivity, "Can't send right now, please try again",
Toast.LENGTH_SHORT
).show()
}
}
} else {
Toast.makeText(
this@ChatActivity, "Please Enter Message",
Toast.LENGTH_SHORT
).show()
}
}
}
- The next step is to display the messages others send. For this,
subscribeto theCHATtopic and display all the messages.
class ChatActivity : AppCompatActivity() {
// PubSubMessageListener
val pubSubMessageListener = object : PubSubMessageListener {
override fun onMessageReceived(message: PubSubMessage) {
Log.d("VideoSDK", "onMessageReceived: ${message.message}")
}
override fun onOldMessagesReceived(messages: List<PubSubMessage>, info: PubSubHistoryInfo) {
// Persisted message list, delivered in batches
Log.d("VideoSDK", "onOldMessagesReceived: $messages")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
//...
// Subscribe for 'CHAT' topic
lifecycleScope.launch {
try {
room!!.pubSub.subscribe("CHAT", pubSubMessageListener)
} catch (e: VideoSDKException) {
// Room is not joined, is reconnecting, or the subscription failed
Log.d("VideoSDK", "subscribe failed: ${e.code} ${e.message}")
}
}
}
}
- The final step is to unsubscribe from the topic once you no longer need it. Here we unsubscribe from the
CHATtopic when the activity is destroyed.
If the activity is destroyed after the room has been left or while it is RECONNECTING, unsubscribe() throws a VideoSDKException. Since onDestroy() frequently runs after the room is left, guard the call as shown below. The activity's lifecycleScope is already cancelled in onDestroy(), so run the call on a scope that outlives the activity.
class ChatActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
//...
}
override fun onDestroy() {
// Unsubscribe for 'CHAT' topic
room?.let { room ->
MainScope().launch {
try {
room.pubSub.unsubscribe("CHAT", pubSubMessageListener)
} catch (e: VideoSDKException) {
// Room already left or reconnecting
Log.d("VideoSDK", "unsubscribe skipped: ${e.code} ${e.message}")
}
}
}
super.onDestroy()
}
}
Private Chat
To turn the above example into a private chat between two participants, pass the sendOnly parameter in PubSubPublishOptions.
class ChatActivity : AppCompatActivity() {
//..
private fun sendMessage() {
// get message from EditText
val message: String = etmessage.text.toString()
if (!TextUtils.isEmpty(message)) {
// Pass the participantId of the participant to whom you want to send the message.
val sendOnly: Array<String> = arrayOf("xyz")
val publishOptions = PubSubPublishOptions(persist = true, sendOnly = sendOnly)
// Sending the Message using the publish method
lifecycleScope.launch {
try {
room!!.pubSub.publish("CHAT", message, publishOptions)
// Clearing the message input
etmessage.setText("")
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "publish failed: ${e.code} ${e.message}")
}
}
} else {
Toast.makeText(
this@ChatActivity, "Please Enter Message",
Toast.LENGTH_SHORT
).show()
}
}
}
High-volume Chat
In a large room a chat topic can produce more messages than the UI can render. Subscribe with PubSubSubscribeOptions to cap how much history you load and to control what happens when live messages arrive faster than your app handles them.
Every live message reaches onMessageReceived(). When batching is on for the room, onBatchReceived() delivers the same messages again as a group, so add each message to your list in only one of the two.
// Backing state for your chat UI
val chatMessages = mutableStateListOf<PubSubMessage>()
var isHistoryLoaded by mutableStateOf(false)
val pubSubMessageListener = object : PubSubMessageListener {
override fun onMessageReceived(message: PubSubMessage) {
// Every live message arrives here, batched or not
chatMessages.add(message)
}
override fun onOldMessagesReceived(messages: List<PubSubMessage>, info: PubSubHistoryInfo) {
chatMessages.addAll(messages)
if (info.isLast) isHistoryLoaded = true
}
override fun onMessageDrop(info: PubSubMessageDropInfo) {
Log.w("VideoSDK", "chat is running hot — dropped ${info.droppedCount} messages")
}
}
private fun subscribeChat() {
val options = PubSubSubscribeOptions(
oldMessageLimit = 100, // only load the last 100 messages
realtimeOverflow = RealtimeOverflow.QUEUE,
maxQueue = 70,
)
lifecycleScope.launch {
try {
room!!.pubSub.subscribe("CHAT", pubSubMessageListener, options)
} catch (e: VideoSDKException) {
// 3073: invalid options, or this listener is already subscribed to the topic
// 3035 / 3065: the room is not joined yet, or is reconnecting
// 4088: the subscription failed
Log.d("VideoSDK", "subscribe failed: ${e.code} ${e.message}")
}
}
}
Handling failures
Each method suspends until the server has answered, and throws a VideoSDKException when the operation fails: code 4087 (PUBSUB_PUBLISH_FAILED) when a message could not be sent, 4088 (PUBSUB_SUBSCRIBE_FAILED) when the subscription failed, and 4089 (PUBSUB_UNSUBSCRIBE_FAILED) when the unsubscribe failed. The same failures are also reported through onError on the RoomEventListener. See the Error Events guide.
Downloading Chat Messages
All the messages from PubSub published with persist : true can be downloaded as a .csv file. This file will be available in the VideoSDK dashboard as well as through the Sessions API.
API Reference
The API references for all the methods and events utilised in this guide are provided below.
Got a Question? Ask us on discord

