Chat using PubSub - Android
For communication or any kind of messaging between participants, VideoSDK provides the pubSub class, which uses the Publish-Subscribe mechanism and can be used to develop a wide variety of functionalities. For example, participants could use it to send chat messages to each other, share files or other media, or even trigger actions like muting or unmuting audio or video.
Now we will see how we can use PubSub to implement Chat functionality. If you are not familiar with the PubSub mechanism and the pubSub class, you can follow this guide.
publish(), subscribe() and unsubscribe() all require a joined meeting. They throw an Exception if the meeting has not reached the CONNECTED state or is RECONNECTING. The examples below open the chat screen during an ongoing meeting, so the meeting is already connected — if instead you subscribe as part of your join flow, do it from onMeetingJoined().
Implementing Chat
Group Chat
- First step in creating a group chat is choosing the topic which all the participants will publish and subscribe to send and receive the messages. We will be using
CHATas the topic for this one. - On the send button, publish the message that the sender typed in the
EditTextfield.
- Kotlin
- Java
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 live.videosdk.rtc.android.Meeting
import live.videosdk.rtc.android.listeners.PubSubMessageListener
import live.videosdk.rtc.android.model.PubSubPublishOptions
class ChatActivity : AppCompatActivity() {
// Meeting
var meeting: Meeting? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
/**
* Here, we have created 'MainApplication' class, which extends android.app.Application class.
* It has Meeting property and getter and setter methods of Meeting 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 Meeting property.
*
* For Example: (MainActivity.kt)
* var meeting = VideoSDK.initMeeting(context, meetingId, ParticipantName, micEnabled, webcamEnabled, paricipantId, mode, multiStream, customTrack, metaData, signalingBaseUrl,preferredProtocol)
* (this.application as MainApplication).meeting = meeting
*/
// Get Meeting
meeting = (this.application as MainApplication).meeting
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()
publishOptions.isPersist = true
// Sending the Message using the publish method
try {
meeting!!.pubSub.publish("CHAT", message, publishOptions)
// Clearing the message input
etmessage.setText("")
} catch (e: Exception) {
// Meeting is not connected yet, or is reconnecting
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()
}
}
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import java.util.List;
import live.videosdk.rtc.android.Meeting;
import live.videosdk.rtc.android.lib.PubSubMessage;
import live.videosdk.rtc.android.listeners.PubSubMessageListener;
import live.videosdk.rtc.android.model.PubSubPublishOptions;
public class ChatActivity extends AppCompatActivity {
// Meeting
Meeting meeting;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
/**
* Here, we have created 'MainApplication' class, which extends android.app.Application class.
* It has Meeting property and getter and setter methods of Meeting 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.java, we have set Meeting property.
*
* For Example: (MainActivity.java)
* Meeting meeting = VideoSDK.initMeeting(context, meetingId, ParticipantName, micEnabled, webcamEnabled, participantId, mode, multiStream, customTrack,metaData, signalingBaseUrl,preferredProtocol);
* ((MainApplication) this.getApplication()).setMeeting(meeting);
*/
// Get Meeting
meeting = ((MainApplication) this.getApplication()).getMeeting();
findViewById(R.id.btnSend).setOnClickListener(view -> sendMessage());
}
private void sendMessage()
{
// get message from EditText
String message = etmessage.getText().toString();
if (!message.equals("")) {
PubSubPublishOptions publishOptions = new PubSubPublishOptions();
publishOptions.setPersist(true);
// Sending the Message using the publish method
try {
meeting.pubSub.publish("CHAT", message, publishOptions);
// Clearing the message input
etmessage.setText("");
} catch (Exception e) {
// Meeting is not connected yet, or is reconnecting
Toast.makeText(ChatActivity.this, "Can't send right now, please try again",
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(ChatActivity.this, "Please Enter Message",
Toast.LENGTH_SHORT).show();
}
}
}
- Next step would be to display the messages others send. For this we have to
subscribeto that topic i.eCHATand display all the messages.
- Kotlin
- Java
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>) {
// Persisted message list
Log.d("VideoSDK", "onOldMessagesReceived: $messages")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
//...
// Subscribe for 'CHAT' topic
try {
meeting!!.pubSub.subscribe("CHAT", pubSubMessageListener)
} catch (e: Exception) {
// Meeting is not connected yet, or is reconnecting
Log.d("VideoSDK", "subscribe failed: ${e.message}")
}
}
}
public class ChatActivity extends AppCompatActivity {
// PubSubMessageListener
PubSubMessageListener pubSubMessageListener = new PubSubMessageListener() {
@Override
public void onMessageReceived(PubSubMessage message) {
Log.d("VideoSDK", "onMessageReceived: " + message.getMessage());
}
@Override
public void onOldMessagesReceived(List<PubSubMessage> messages) {
// Persisted message list
Log.d("VideoSDK", "onOldMessagesReceived: " + messages);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
//..
// Subscribe for 'CHAT' topic
try {
meeting.pubSub.subscribe("CHAT", pubSubMessageListener);
} catch (Exception e) {
// Meeting is not connected yet, or is reconnecting
Log.d("VideoSDK", "subscribe failed: " + e.getMessage());
}
}
}
- Final step in the group chat would be
unsubscribeto that topic, which you had previously subscribed but no longer needed. Here we areunsubscribetoCHATtopic on activity destroy.
If the activity is destroyed after the meeting has already ended or while it is RECONNECTING, unsubscribe() throws an Exception. Since onDestroy() frequently runs after the meeting is over, guard the call as shown below.
- Kotlin
- Java
class ChatActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
//...
}
override fun onDestroy() {
// Unsubscribe for 'CHAT' topic
try {
meeting?.pubSub?.unsubscribe("CHAT", pubSubMessageListener)
} catch (e: Exception) {
// Meeting already ended or reconnecting — nothing left to unsubscribe from
Log.d("VideoSDK", "unsubscribe skipped: ${e.message}")
}
super.onDestroy()
}
}
public class ChatActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
//..
}
@Override
protected void onDestroy() {
// Unsubscribe for 'CHAT' topic
try {
meeting.pubSub.unsubscribe("CHAT", pubSubMessageListener);
} catch (Exception e) {
// Meeting already ended or reconnecting — nothing left to unsubscribe from
Log.d("VideoSDK", "unsubscribe skipped: " + e.getMessage());
}
super.onDestroy();
}
}
Private Chat
In the above example, if you want to convert into the private chat between two participants, then all you have to do is pass sendOnly parameter in PubSubPublishOptions.
- Kotlin
- Java
class ChatActivity : AppCompatActivity() {
//..
private fun sendMessage() {
// get message from EditText
val message: String = etmessage.text.toString()
if (!TextUtils.isEmpty(message)) {
val publishOptions = PubSubPublishOptions()
publishOptions.isPersist = true
// Pass the participantId of the participant to whom you want to send the message.
val sendOnly: Array<String> = arrayOf("xyz")
publishOptions.setSendOnly(sendOnly)
// Sending the Message using the publish method
try {
meeting!!.pubSub.publish("CHAT", message, publishOptions)
// Clearing the message input
etmessage.setText("")
} catch (e: Exception) {
Log.d("VideoSDK", "publish failed: ${e.message}")
}
} else {
Toast.makeText(
this@ChatActivity, "Please Enter Message",
Toast.LENGTH_SHORT
).show()
}
}
}
public class ChatActivity extends AppCompatActivity {
//...
private void sendMessage()
{
// get message from EditText
String message = etmessage.getText().toString();
if (!message.equals("")) {
PubSubPublishOptions publishOptions = new PubSubPublishOptions();
publishOptions.setPersist(true);
// Pass the participantId of the participant to whom you want to send the message.
String[] sendOnly = {
"xyz"
};
publishOptions.setSendOnly(sendOnly);
// Sending the Message using the publish method
try {
meeting.pubSub.publish("CHAT", message, publishOptions);
// Clearing the message input
etmessage.setText("");
} catch (Exception e) {
Log.d("VideoSDK", "publish failed: " + e.getMessage());
}
} else {
Toast.makeText(ChatActivity.this, "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 outpace your consumer.
- Kotlin
- Java
// Backing state for your chat UI
val chatMessages = mutableStateListOf<PubSubMessage>()
val pubSubMessageListener = object : PubSubMessageListener {
override fun onMessageReceived(message: PubSubMessage) {
chatMessages.add(message)
}
override fun onBatchReceived(messages: List<PubSubMessage>) {
// One callback per live batch — add in bulk instead of one at a time
chatMessages.addAll(messages)
}
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()
options.oldMessageLimit = 100 // only load the last 100 messages
options.realtimeOverflow = PubSubSubscribeOptions.RealtimeOverflow.QUEUE
options.maxQueue = 70
try {
meeting!!.pubSub.subscribe("CHAT", pubSubMessageListener, options)
} catch (e: IllegalArgumentException) {
// Invalid options, or this listener is already subscribed to the topic
Log.d("VideoSDK", "subscribe rejected: ${e.message}")
} catch (e: Exception) {
// Meeting is not connected yet, or is reconnecting
Log.d("VideoSDK", "subscribe failed: ${e.message}")
}
}
// Backing list for your chat UI
private final List<PubSubMessage> chatMessages = new ArrayList<>();
PubSubMessageListener pubSubMessageListener = new PubSubMessageListener() {
@Override
public void onMessageReceived(PubSubMessage message) {
chatMessages.add(message);
}
@Override
public void onBatchReceived(List<PubSubMessage> messages) {
// One callback per live batch — add in bulk instead of one at a time
chatMessages.addAll(messages);
}
@Override
public void onOldMessagesReceived(List<PubSubMessage> messages, PubSubHistoryInfo info) {
chatMessages.addAll(messages);
if (info.isLast()) isHistoryLoaded = true;
}
@Override
public void onMessageDrop(PubSubMessageDropInfo info) {
Log.w("VideoSDK", "chat is running hot — dropped " + info.getDroppedCount() + " messages");
}
};
private void subscribeChat() {
PubSubSubscribeOptions options = new PubSubSubscribeOptions();
options.setOldMessageLimit(100); // only load the last 100 messages
options.setRealtimeOverflow(PubSubSubscribeOptions.RealtimeOverflow.QUEUE);
options.setMaxQueue(70);
try {
meeting.pubSub.subscribe("CHAT", pubSubMessageListener, options);
} catch (IllegalArgumentException e) {
// Invalid options, or this listener is already subscribed to the topic
Log.d("VideoSDK", "subscribe rejected: " + e.getMessage());
} catch (Exception e) {
// Meeting is not connected yet, or is reconnecting
Log.d("VideoSDK", "subscribe failed: " + e.getMessage());
}
}
Handling failures
If the server rejects a chat operation, the SDK reports it through onError on the MeetingEventListener rather than by throwing: code 4087 (PUBSUB_PUBLISH_FAILED) when a message could not be delivered, 4088 (PUBSUB_SUBSCRIBE_FAILED) when the subscription was rejected, and 4089 (PUBSUB_UNSUBSCRIBE_FAILED). 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

