Chat during Live Stream - Flutter
Enhance your live stream experience by enabling real-time audience chat using VideoSDK's PubSub class. Whether you’re streaming a webinar, online event, or an interactive session, integrating a chat system lets your viewers engage, ask questions, and react instantly. This guide shows how to build a group or private chat interface for a live stream using the Publish-Subscribe (PubSub) mechanism.
This guide focuses on using PubSub to implement Chat functionality. If you are not familiar with the PubSub mechanism and PubSub class, you can follow this guide.
PubSub calls need a joined room, so show the chat, and subscribe, after Events.roomJoined. publish(), subscribe() and unsubscribe() return a Future that throws a VideoSDKError when the SDK refuses the call, for example 3035 when the room is not joined. Every call site needs a catch or a .catchError — an unhandled one becomes a runtime async error. See the Migration Guide.
Implementing Chat
Group Chat(Public Chat for All Viewers)
- 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. So let us lets create a message input and send button to publish the messages using thepubSubfrom theRoomobject.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class ChatView extends StatefulWidget {
final Room room;
...
}
class _ChatViewState extends State<ChatView> {
final msgTextController = TextEditingController();
@override
void initState() {
...
}
@override
Widget build(BuildContext context) {
return Column(
children:[
Row(
children: [
Expanded(
child: TextField(
style: TextStyle(
fontSize:16,
fontWeight: FontWeight.w500,
),
controller: msgTextController,
onChanged: (value) => setState(() {
msgTextController.text;
}),
decoration: const InputDecoration(
hintText: "Write your message",
border: InputBorder.none,
),
),
),
ElevatedButton(
onPressed:() async {
if(!msgTextController.text.trim().isEmpty){
try {
await widget.room.pubSub.publish(
"CHAT",
msgTextController.text,
const PubSubPublishOptions(persist: true),
);
msgTextController.clear();
} on VideoSDKError catch (e) {
// Not sent — leave the text in place so it can be retried.
debugPrint("Publish failed: ${e.code} ${e.name}");
} catch (e) {
// Anything else that went wrong.
debugPrint("Publish failed: $e");
}
}
},
child: const Text("Send Message"),
),
],
),
]
);
}
}
- Final step in the group chat would be to display the messages others send. For this we will subscribe to the topic
CHATwith aPubSubMessageListenerand keep every message it receives. New messages arrive ononMessageReceived. Messages published withpersist: truebefore you subscribed arrive ononOldMessagesReceived, so the list starts with the chat history. Pass the same listener object tounsubscribe().
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class ChatView extends StatefulWidget {
final Room room;
...
}
class _ChatViewState extends State<ChatView> {
// Messages on the 'CHAT' topic
final List<PubSubMessage> messages = [];
// Pass this same object to unsubscribe()
late final PubSubMessageListener chatListener = PubSubMessageListener(
// Chat history, sent with persist: true before you subscribed
onOldMessagesReceived: (List<PubSubMessage> history, PubSubHistoryInfo info) {
if (mounted) setState(() => messages.addAll(history));
},
// Every new message on the topic
onMessageReceived: messageHandler,
);
@override
void initState() {
...
// Subscribing 'CHAT' Topic
subscribeToChat();
}
Future<void> subscribeToChat() async {
try {
await widget.room.pubSub.subscribe("CHAT", chatListener);
} catch (e) {
// Called from initState(), which cannot await, so failures are caught here.
debugPrint("Subscribe failed: $e");
}
}
//Handler which will be called for every new message on the topic
void messageHandler(PubSubMessage message) {
if (mounted) {
setState(() => messages.add(message));
}
}
@override
Widget build(BuildContext context) {
return Column(
children:[
Expanded(
child: SingleChildScrollView(
reverse: true,
child: Column(
children: messages
.map(
(message) => Text(
message.message
),
)
.toList(),
),
),
),
...
//Send Message code Here
]
);
}
@override
void dispose() {
// Unsubscribe. dispose() cannot await, so the failure is logged instead.
// The listener is removed even when this call fails.
widget.room.pubSub
.unsubscribe("CHAT", chatListener)
.catchError((Object e) => debugPrint("Unsubscribe failed: $e"));
super.dispose();
}
}
Private Chat (1:1 between Host and Viewer)
Private messaging is ideal when a host or moderator needs to directly respond to a viewer’s question. This can be achieved using the sendOnly parameter in PubSubPublishOptions. A participant listed in sendOnly receives the message only if they are subscribed to the topic.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class ChatView extends StatefulWidget {
final Room room;
...
}
class _ChatViewState extends State<ChatView> {
//...
@override
Widget build(BuildContext context) {
return Column(
children:[
Row(
children: [
Expanded(
child: TextField(
style: TextStyle(
fontSize:16,
fontWeight: FontWeight.w500,
),
controller: msgTextController,
onChanged: (value) => setState(() {
msgTextController.text;
}),
decoration: const InputDecoration(
hintText: "Write your message",
border: InputBorder.none,
),
),
),
ElevatedButton(
onPressed:() async {
if(!msgTextController.text.trim().isEmpty){
// Pass the participantId of the participant to whom you want to send the message.
try {
await widget.room.pubSub.publish(
"CHAT",
msgTextController.text,
const PubSubPublishOptions(
persist: true, sendOnly: ["xyz"]),
);
msgTextController.clear();
} on VideoSDKError catch (e) {
// Not sent — leave the text in place so it can be retried.
debugPrint("Publish failed: ${e.code} ${e.name}");
} catch (e) {
// Anything else that went wrong.
debugPrint("Publish failed: $e");
}
}
},
child: const Text("Send Message"),
),
],
),
]
);
}
}
Displaying Latest Message Notification
You may want to show the notification to the user when new message arrives. So lets continue our example and add alert for the new messages. Stored messages arrive on onOldMessagesReceived, so alerting from onMessageReceived shows only new ones.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class ChatView extends StatefulWidget {
final Room room;
...
}
class _ChatViewState extends State<ChatView> {
@override
void initState() {
...
}
//Handler for new messages only, so every one gets a snackbar
void messageHandler(PubSubMessage message) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
message.message,
overflow: TextOverflow.fade,
),
));
setState(() => messages.add(message));
}
@override
Widget build(BuildContext context) {
return Column(
children:[
...
]
);
}
@override
void dispose() {
...
}
}
Downloading Chat Messages
All the messages from the PubSub which where published with persist : true and can be downloaded as an .csv file. This file will be available in the VideoSDK dashboard as well as throught 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

