Build a virtual event platform using Firestore collections for events, attendees, and Q&A questions. Embed live video via a WebView Custom Widget pointing to YouTube Live or similar streaming URLs. Add real-time chat with a reversed ListView of Firestore messages, and a Q&A panel where attendees submit and upvote questions for the host to address during the broadcast.
Building a Virtual Event Platform in FlutterFlow
Virtual events are essential for webinars, conferences, and workshops. This tutorial walks you through building a one-to-many broadcast platform with audience interaction features including live chat, Q&A with upvoting, attendee registration, and post-event recording access — all wired into FlutterFlow's visual builder with Firestore as the backend.
Prerequisites
- A FlutterFlow project on the Pro plan or higher
- Firebase project with Firestore and Cloud Functions enabled
- A streaming service account (YouTube Live, Vimeo Live, or similar) for the video embed URL
- Basic familiarity with Firestore collections and Backend Queries in FlutterFlow
Step-by-step guide
Create the Firestore collections for events and attendees
Create the Firestore collections for events and attendees
In the Firebase Console, create an `events` collection with fields: title (String), description (String), startTime (Timestamp), endTime (Timestamp), type (String — webinar, conference, or workshop), streamUrl (String), maxAttendees (int), hostId (String), recordingUrl (String), and isLive (bool). Then create a subcollection `events/{eventId}/attendees` with fields: userId (String), registeredAt (Timestamp), and hasJoined (bool). Back in FlutterFlow, go to the Firestore tab and import both schemas so they appear in your Backend Query options.
Expected result: Both collections appear in FlutterFlow's Firestore schema panel and are available for Backend Queries.
Build the event listing page with registration
Build the event listing page with registration
Create an Events page with a Backend Query on the `events` collection ordered by startTime ascending. Drag a ListView and bind it to the query results. Inside each list item, add a Container with the event title (Text), date and time formatted with a Custom Function, event type badge (Container with colored background), and attendee count. Add a Register button that creates a new document in the `attendees` subcollection with the current user's ID, sets registeredAt to the current timestamp, and hasJoined to false. Use Conditional Visibility to show 'Registered' text instead of the button if the user already has an attendee document.
Expected result: Users see upcoming events in a list and can register with a single tap. The button switches to a Registered label after signing up.
Embed the live video stream with a WebView Custom Widget
Embed the live video stream with a WebView Custom Widget
Create a Custom Widget named LiveStreamPlayer with a Component Parameter streamUrl (String). In the build method, return a SizedBox wrapping a WebView widget that loads the streamUrl (e.g., a YouTube Live embed URL like https://www.youtube.com/embed/VIDEO_ID?autoplay=1). Set the widget height to 250 and width to double.infinity. On the Event Detail page, add this Custom Widget and bind the streamUrl parameter to the event document's streamUrl field. Wrap it in Conditional Visibility that checks event.isLive == true so the player only appears during live broadcasts.
1// Custom Widget: LiveStreamPlayer2import 'package:flutter/material.dart';3import 'package:webview_flutter/webview_flutter.dart';45class LiveStreamPlayer extends StatefulWidget {6 final double width;7 final double height;8 final String streamUrl;910 const LiveStreamPlayer({11 Key? key,12 required this.width,13 required this.height,14 required this.streamUrl,15 }) : super(key: key);1617 @override18 State<LiveStreamPlayer> createState() => _LiveStreamPlayerState();19}2021class _LiveStreamPlayerState extends State<LiveStreamPlayer> {22 late final WebViewController _controller;2324 @override25 void initState() {26 super.initState();27 _controller = WebViewController()28 ..setJavaScriptMode(JavaScriptMode.unrestricted)29 ..loadRequest(Uri.parse(widget.streamUrl));30 }3132 @override33 Widget build(BuildContext context) {34 return SizedBox(35 width: widget.width,36 height: widget.height,37 child: WebViewWidget(controller: _controller),38 );39 }40}Expected result: The live video stream renders inside the event detail page when the event is marked as live.
Add real-time chat alongside the stream
Add real-time chat alongside the stream
Create a subcollection `events/{eventId}/messages` with fields: senderId (String), displayName (String), text (String), and timestamp (Timestamp). On the Event Detail page below the video player, add a Column with a ListView bound to a Backend Query on the messages subcollection ordered by timestamp descending. Set Single Time Query to OFF for real-time updates. Inside each list item, show the sender's displayName in bold followed by the message text. At the bottom, add a Row with a TextField for message input and a Send IconButton. The Send action creates a new document in the messages subcollection with the current user info and the text value, then clears the TextField.
Expected result: Attendees see live chat messages appearing in real time and can send their own messages during the event.
Build the Q&A panel with upvoting
Build the Q&A panel with upvoting
Create a subcollection `events/{eventId}/questions` with fields: userId (String), displayName (String), text (String), upvotes (int), isAnswered (bool), and timestamp (Timestamp). Add a TabBar on the event detail page with two tabs: Chat and Q&A. In the Q&A tab, add a ListView bound to the questions subcollection ordered by upvotes descending. Each item shows the question text, author name, upvote count, and an upvote IconButton. The upvote action uses FieldValue.increment(1) on the upvotes field. To prevent double-voting, create a subcollection `questions/{questionId}/voters` and check for the current user's document before allowing the upvote. The host sees an 'Mark Answered' button (Conditional Visibility: currentUser.uid == event.hostId) that sets isAnswered to true.
Expected result: Attendees submit questions and upvote others. The most popular questions rise to the top for the host to address.
Handle post-event recording and feedback
Handle post-event recording and feedback
After the event ends, the host updates the event document to set isLive to false and add the recordingUrl field pointing to the saved stream recording. On the Event Detail page, add a Conditional Visibility block that shows when isLive is false AND recordingUrl is not empty — display a Video Player widget or WebView with the recording URL. Below it, add a feedback section: a StarRating widget (1-5) and a TextField for comments. On submit, create a document in `events/{eventId}/feedback` with the user's rating, comment, and timestamp. Show a summary of average rating using a Custom Function that queries the feedback subcollection.
Expected result: After the event, attendees can watch the recording and leave feedback. The average rating displays below the recording.
Complete working example
1// Custom Widget: LiveStreamPlayer2// Pubspec: webview_flutter: ^4.4.034import 'package:flutter/material.dart';5import 'package:webview_flutter/webview_flutter.dart';67class LiveStreamPlayer extends StatefulWidget {8 final double width;9 final double height;10 final String streamUrl;1112 const LiveStreamPlayer({13 Key? key,14 required this.width,15 required this.height,16 required this.streamUrl,17 }) : super(key: key);1819 @override20 State<LiveStreamPlayer> createState() => _LiveStreamPlayerState();21}2223class _LiveStreamPlayerState extends State<LiveStreamPlayer> {24 late final WebViewController _controller;2526 @override27 void initState() {28 super.initState();29 _controller = WebViewController()30 ..setJavaScriptMode(JavaScriptMode.unrestricted)31 ..loadRequest(Uri.parse(widget.streamUrl));32 }3334 @override35 Widget build(BuildContext context) {36 return SizedBox(37 width: widget.width,38 height: widget.height,39 child: WebViewWidget(controller: _controller),40 );41 }42}4344// Cloud Function: sendEventReminder45// Triggered by Cloud Scheduler 30 min before event startTime46// Queries attendees subcollection and sends FCM push47// notification with event title and join link.4849// Firestore Structure:50// events/{eventId}51// - title, description, startTime, endTime52// - type, streamUrl, maxAttendees, hostId53// - recordingUrl, isLive54// events/{eventId}/attendees/{attendeeId}55// - userId, registeredAt, hasJoined56// events/{eventId}/messages/{messageId}57// - senderId, displayName, text, timestamp58// events/{eventId}/questions/{questionId}59// - userId, displayName, text, upvotes60// - isAnswered, timestamp61// events/{eventId}/feedback/{feedbackId}62// - userId, rating, comment, timestampCommon mistakes when creating a Virtual Event Platform in FlutterFlow
Why it's a problem: Using bidirectional video calls (Agora) for large webinars with hundreds of attendees
How to avoid: Use one-to-many streaming services like YouTube Live, Vimeo Live, or Agora Broadcast mode. Embed the stream URL in a WebView and use Firestore chat/Q&A for audience interaction.
Why it's a problem: Not setting Single Time Query to OFF on the chat and Q&A ListViews
How to avoid: Set Single Time Query to OFF on all Backend Queries for messages and questions so Firestore real-time listeners push updates to the UI automatically.
Why it's a problem: Allowing unlimited registrations beyond the event capacity
How to avoid: Before creating an attendee document, query the attendees subcollection count and compare it to maxAttendees. If full, show a 'Sold Out' message instead of the Register button.
Best practices
- Use Conditional Visibility to show the live stream only when isLive is true, and the recording when available after the event ends
- Order Q&A questions by upvotes descending so the host always sees the most popular questions first
- Add a countdown timer on the event detail page using a Custom Function that calculates the difference between now and startTime
- Store the host's userId on the event document so you can conditionally show admin controls like 'Go Live' and 'Mark Answered'
- Limit chat message length to 500 characters to prevent spam and keep the conversation readable
- Send FCM push notifications 30 minutes before the event starts via a scheduled Cloud Function to remind registered attendees
- Use a TabBar to separate Chat and Q&A panels so the event detail page stays organized
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building a virtual event platform in FlutterFlow with Firestore. I need collections for events, attendees, live chat messages, and Q&A with upvoting. Show me the Firestore schema design and explain how to embed a YouTube Live stream in a WebView Custom Widget.
Create a virtual event page with a live stream video player at the top, a live chat panel below it with real-time Firestore messages, and a Q&A tab where attendees can submit and upvote questions. Add a Register button that creates an attendee document.
Frequently asked questions
Can I use Agora instead of YouTube Live for the video stream?
Yes. Agora supports broadcast mode for one-to-many streaming. Use the Agora Flutter SDK in a Custom Widget instead of a WebView. However, YouTube Live is simpler to integrate and has no per-minute streaming costs.
How do I prevent users from upvoting a Q&A question more than once?
Create a voters subcollection under each question document. Before incrementing upvotes, check if a document with the current user's ID already exists. If it does, show a 'You already voted' message instead of incrementing.
Can I limit the number of attendees for an event?
Yes. Store a maxAttendees field on the event document. Before creating an attendee document, query the attendees subcollection count. If it equals or exceeds maxAttendees, disable the Register button and show 'Event Full'.
How do I add the event to the attendee's calendar?
Use a Cloud Function that generates an .ics calendar file with the event title, start time, and end time. Attach it to the registration confirmation email or provide a download link on the event detail page.
Does the live chat work on both mobile and web?
Yes. The Firestore real-time listener powering the chat ListView works identically on iOS, Android, and FlutterFlow web builds.
Can RapidDev help build a production-ready virtual event platform?
Yes. RapidDev can implement multi-track conferences with breakout rooms, ticketing with Stripe, speaker management dashboards, and automated recording workflows.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation