Audience Polls during Live Stream - Flutter
Interactive polls are a great way to increase engagement during livestreams. Using VideoSDK’s PubSub mechanism, you can easily implement real-time audience polling, where viewers can vote and the host can see live results instantly.
This guide walks you through how to create, send, and visualize poll results during a livestream.
Step 1: Creating and Publishing a Poll
To initiate a poll, use the PubSub object with a POLL topic. The poll structure should include a question and multiple options. This message will be published to all participants.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class PollHostView extends StatelessWidget {
final Room room;
const PollHostView({required this.room});
void startPoll() {
final pollData = {
"id": DateTime.now().millisecondsSinceEpoch,
"question": "Which feature do you love the most?",
"options": ["Reactions", "Screen Share", "Whiteboard", "Polls"],
"responses": {},
};
room.pubSub.publish(
"POLL",
jsonEncode(pollData),
const PubSubPublishOptions(persist: true),
);
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: startPoll,
child: const Text("Start Poll"),
);
}
}
Step 2: Subscribing to Polls and Displaying Options
Participants can listen to the POLL topic and render voting options dynamically based on the incoming data. The senderId of the received poll message identifies the host. Publish the vote with sendOnly set to that id, so only the host receives it and other participants can't see who voted for what.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class PollAudienceView extends StatefulWidget {
final Room room;
const PollAudienceView({required this.room});
@override
State<PollAudienceView> createState() => _PollAudienceViewState();
}
class _PollAudienceViewState extends State<PollAudienceView> {
Map<String, dynamic>? activePoll;
String? hostId;
@override
void initState() {
super.initState();
widget.room.pubSub.subscribe("POLL", handlePollReceived);
}
@override
void dispose() {
widget.room.pubSub.unsubscribe("POLL", handlePollReceived);
super.dispose();
}
void handlePollReceived(PubSubMessage message) {
setState(() {
activePoll = jsonDecode(message.message);
hostId = message.senderId;
});
}
void submitVote(String option) {
if (activePoll != null && hostId != null) {
widget.room.pubSub.publish(
"POLL_RESPONSE",
jsonEncode({
"pollId": activePoll!['id'],
"option": option,
}),
PubSubPublishOptions(persist: false, sendOnly: [hostId!]),
);
}
}
@override
Widget build(BuildContext context) {
if (activePoll == null) return const SizedBox();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
activePoll!['question'],
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
...(activePoll!['options'] as List<dynamic>).map((option) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: ElevatedButton(
onPressed: () => submitVote(option.toString()),
child: Text(option.toString()),
),
);
}).toList(),
],
);
}
}
Step 3: Aggregating and Displaying Poll Results
The host can subscribe to the POLL_RESPONSE topic to collect responses and render the result in real-time.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class PollResultsView extends StatefulWidget {
final Room room;
const PollResultsView({required this.room});
@override
State<PollResultsView> createState() => _PollResultsViewState();
}
class _PollResultsViewState extends State<PollResultsView> {
final Map<String, Map<String, int>> results = {};
@override
void initState() {
super.initState();
widget.room.pubSub.subscribe("POLL_RESPONSE", handleResponse);
}
@override
void dispose() {
widget.room.pubSub.unsubscribe("POLL_RESPONSE", handleResponse);
super.dispose();
}
void handleResponse(PubSubMessage message) {
final data = jsonDecode(message.message);
final pollId = data['pollId'].toString();
final option = data['option'].toString();
setState(() {
final pollResult = results[pollId] ?? {};
pollResult[option] = (pollResult[option] ?? 0) + 1;
results[pollId] = pollResult;
});
}
@override
Widget build(BuildContext context) {
if (results.isEmpty) return const Text("No poll results yet.");
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: results.entries.map((entry) {
final pollId = entry.key;
final pollResult = entry.value;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Poll ID: $pollId", style: const TextStyle(fontWeight: FontWeight.bold)),
...pollResult.entries.map((e) => Text("${e.key}: ${e.value} votes")).toList(),
const SizedBox(height: 16),
],
);
}).toList(),
);
}
}
API Reference
The API references for all the methods and events utilized in this guide are provided below.
Got a Question? Ask us on discord

