Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Develop a Virtual Classroom Feature in FlutterFlow

Build a live virtual classroom by combining an Agora video Custom Widget with educational interaction tools. Students join a session with video and audio, raise their hand to enter a queue the instructor manages, participate in real-time quiz polls, and access shared resources. Attendance is tracked automatically via Firestore when students join, and all session data persists for review after class ends.

What you'll learn

  • How to set up an Agora video grid Custom Widget for classroom sessions
  • How to implement a hand-raise queue that the instructor manages in real time
  • How to run live quiz polls during class and display results instantly
  • How to track attendance and share resources during the session
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read30-35 minFlutterFlow Pro+ (Custom Code required for Agora SDK)March 2026RapidDev Engineering Team
TL;DR

Build a live virtual classroom by combining an Agora video Custom Widget with educational interaction tools. Students join a session with video and audio, raise their hand to enter a queue the instructor manages, participate in real-time quiz polls, and access shared resources. Attendance is tracked automatically via Firestore when students join, and all session data persists for review after class ends.

Building a Virtual Classroom Feature in FlutterFlow

A virtual classroom goes beyond basic video conferencing by adding educational tools like hand-raising, live quizzes, attendance tracking, and resource sharing. This tutorial builds a fully interactive classroom where instructors teach, students participate, and all interactions are recorded in Firestore for post-session review.

Prerequisites

  • A FlutterFlow project on the Pro plan or higher
  • An Agora.io account with an App ID for real-time video
  • Firebase project with Firestore and Cloud Functions enabled
  • Basic familiarity with FlutterFlow Custom Widgets and Action Flows

Step-by-step guide

1

Design the Firestore schema for classroom sessions

Create a `sessions` collection with fields: title (String), instructorId (String), startTime (Timestamp), endTime (Timestamp), agoraChannel (String), isLive (bool), and maxStudents (int). Add subcollections: `sessions/{id}/attendance` with userId, joinedAt, and leftAt fields; `sessions/{id}/raised_hands` with userId, displayName, and raisedAt; `sessions/{id}/resources` with title, fileUrl, and sharedAt; and `sessions/{id}/polls` with question, options array, and results map. Import all schemas into FlutterFlow's Firestore panel.

Expected result: All collections and subcollections are defined in Firestore and accessible via FlutterFlow Backend Queries.

2

Build the Agora video grid Custom Widget

Add the agora_rtc_engine package to Pubspec Dependencies. Create a Custom Widget named ClassroomVideo with parameters: appId (String), channelName (String), and isInstructor (bool). Initialize the RtcEngine, enable video, and join the channel with a temporary token. Render local and remote video views in a GridView. The instructor's video appears larger (2x span) at the top, while student videos fill a scrollable grid below. Add mute audio, toggle camera, and leave channel IconButtons at the bottom. When isInstructor is true, show an additional 'Mute All Students' button.

Expected result: The instructor and students see each other in a video grid. Audio and camera controls work, and students join muted by default.

3

Implement the hand-raise queue with instructor controls

Add a Hand Raise button for students. On tap, create a document in `sessions/{id}/raised_hands` with the student's userId, displayName, and raisedAt timestamp. Change the button to 'Lower Hand' using a Conditional check on whether the user's document exists in the subcollection. For the instructor, display a ListView of raised hands ordered by raisedAt ascending in a side panel. Each item shows the student name and two IconButtons: a microphone icon to unmute the student (via Agora SDK's muteRemoteAudioStream) and a dismiss icon to delete the raised_hand document. Set Single Time Query to OFF so the queue updates in real time.

Expected result: Students raise and lower their hands with a toggle button. The instructor sees a real-time queue and can unmute or dismiss students.

4

Create the live quiz poll feature

Add a 'Launch Poll' button visible only to the instructor (Conditional Visibility: isInstructor). On tap, show a dialog with a TextField for the question and four option TextFields. On submit, create a document in `sessions/{id}/polls` with the question, options array, a results map initialized to zero for each option, and isActive set to true. For students, display the active poll as a full-width card with the question and tappable option Containers. On selection, update the results map using FieldValue.increment(1) for the chosen option and create a voter document to prevent double-voting. Show a real-time bar chart of results once the student has voted.

Expected result: The instructor launches a poll, students vote on options, and results update in real time with a bar chart visualization.

5

Track attendance automatically on session join

When a student navigates to the classroom page, add an On Page Load action that creates a document in `sessions/{id}/attendance` with the current user's userId and joinedAt set to the current timestamp. When the student leaves (On Page Dispose or Leave button tap), update the same document to set leftAt to the current timestamp. The instructor can view attendance by opening a BottomSheet with a ListView of the attendance subcollection showing each student's name, join time, and duration calculated by a Custom Function subtracting joinedAt from leftAt.

Expected result: Attendance records are created automatically when students enter the classroom and completed when they leave.

6

Add resource sharing during the live session

Give the instructor a 'Share Resource' button that opens a dialog with a TextField for the resource title and a FlutterFlowUploadButton for the file. On submit, upload the file to Firebase Storage and create a document in `sessions/{id}/resources` with the title, fileUrl, and sharedAt timestamp. Students see a Resources tab with a ListView of all shared resources in real time (Single Time Query OFF). Each item shows the title, timestamp, and a Download button that calls Launch URL with the fileUrl. After the session ends, resources remain accessible on the session detail page for review.

Expected result: The instructor shares files during class and students can download them immediately or after the session.

Complete working example

ClassroomVideo Custom Widget
1// Custom Widget: ClassroomVideo
2// Pubspec: agora_rtc_engine: ^6.2.0
3import 'package:flutter/material.dart';
4import 'package:agora_rtc_engine/agora_rtc_engine.dart';
5
6class ClassroomVideo extends StatefulWidget {
7 final double width;
8 final double height;
9 final String appId;
10 final String channelName;
11 final String token;
12 final bool isInstructor;
13 const ClassroomVideo({Key? key, required this.width,
14 required this.height, required this.appId,
15 required this.channelName, required this.token,
16 required this.isInstructor}) : super(key: key);
17 @override
18 State<ClassroomVideo> createState() => _CVState();
19}
20
21class _CVState extends State<ClassroomVideo> {
22 late RtcEngine _engine;
23 final _remoteUids = <int>[];
24 bool _muted = false, _camOff = false;
25
26 @override
27 void initState() { super.initState(); _init(); }
28
29 Future<void> _init() async {
30 _engine = createAgoraRtcEngine();
31 await _engine.initialize(RtcEngineContext(appId: widget.appId));
32 await _engine.enableVideo();
33 _engine.registerEventHandler(RtcEngineEventHandler(
34 onUserJoined: (c, uid, e) => setState(() => _remoteUids.add(uid)),
35 onUserOffline: (c, uid, r) => setState(() => _remoteUids.remove(uid)),
36 ));
37 if (!widget.isInstructor) {
38 await _engine.muteLocalAudioStream(true);
39 _muted = true;
40 }
41 await _engine.joinChannel(token: widget.token,
42 channelId: widget.channelName, uid: 0,
43 options: const ChannelMediaOptions());
44 }
45
46 @override
47 void dispose() { _engine.leaveChannel(); _engine.release(); super.dispose(); }
48
49 @override
50 Widget build(BuildContext context) {
51 return SizedBox(width: widget.width, height: widget.height,
52 child: Column(children: [
53 Expanded(child: GridView.count(crossAxisCount: 2, children: [
54 AgoraVideoView(controller: VideoViewController(
55 rtcEngine: _engine, canvas: const VideoCanvas(uid: 0))),
56 ..._remoteUids.map((uid) => AgoraVideoView(
57 controller: VideoViewController.remote(rtcEngine: _engine,
58 canvas: VideoCanvas(uid: uid),
59 connection: RtcConnection(channelId: widget.channelName)))),
60 ])),
61 Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
62 IconButton(icon: Icon(_muted ? Icons.mic_off : Icons.mic),
63 onPressed: () { setState(() => _muted = !_muted);
64 _engine.muteLocalAudioStream(_muted); }),
65 IconButton(icon: Icon(_camOff ? Icons.videocam_off : Icons.videocam),
66 onPressed: () { setState(() => _camOff = !_camOff);
67 _engine.muteLocalVideoStream(_camOff); }),
68 IconButton(icon: const Icon(Icons.call_end, color: Colors.red),
69 onPressed: () => _engine.leaveChannel()),
70 ]),
71 ]),
72 );
73 }
74}
75
76// Firestore Schema:
77// sessions/{id} - title, instructorId, startTime, endTime,
78// agoraChannel, isLive, maxStudents
79// sessions/{id}/attendance/{uid} - joinedAt, leftAt
80// sessions/{id}/raised_hands/{uid} - displayName, raisedAt
81// sessions/{id}/resources/{id} - title, fileUrl, sharedAt
82// sessions/{id}/polls/{id} - question, options, results, isActive

Common mistakes when developing a Virtual Classroom Feature in FlutterFlow

Why it's a problem: Not muting all students by default when they join the classroom

How to avoid: Default all students to muted on join by calling muteLocalAudioStream(true) when isInstructor is false. Use the hand-raise workflow so the instructor can unmute individuals selectively.

Why it's a problem: Using a single Firestore document for all raised hands instead of a subcollection

How to avoid: Use a raised_hands subcollection where each student creates their own document. This allows concurrent writes without conflicts and makes querying and ordering straightforward.

Why it's a problem: Not setting the attendance leftAt timestamp when students navigate away

How to avoid: Use an On Page Dispose action or a Leave button tap action to update the attendance document with the leftAt timestamp before the student navigates away.

Why it's a problem: Allowing students to vote multiple times on the same quiz poll

How to avoid: Create a voters subcollection under each poll document. Check for the current user's voter document before allowing a vote. If it exists, show the results instead of the voting options.

Best practices

  • Display the instructor's video feed larger than student feeds using a 2x grid span so the teacher is always the visual focus
  • Order the raised hands queue by raisedAt ascending so the first student to raise their hand gets called on first
  • Set Single Time Query to OFF on all real-time features: raised hands, polls, chat, and resources
  • Show poll results as horizontal percentage bars that update in real time for immediate visual feedback
  • Store all session data (attendance, polls, resources) permanently so both instructor and students can review after class
  • Add a 'Session Ended' overlay that prevents late joins and shows a link to the session recording and materials

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a virtual classroom in FlutterFlow with Agora for video, Firestore for data. I need: video grid, hand-raise queue, live quiz polls, attendance tracking, and resource sharing. Show me the Firestore schema and explain the hand-raise and poll workflows step by step.

FlutterFlow Prompt

Create a virtual classroom page with a video call area at the top, a hand-raise button for students, a raised hands list for the instructor, a live poll feature, and a resources tab. Track attendance when students join and leave.

Frequently asked questions

How many students can join a single virtual classroom session?

Agora supports up to 17 video streams in a standard channel and thousands of audience members in broadcast mode. For classrooms with more than 17 students, switch to broadcast mode where only the instructor and called-on students have active video.

Can I record the classroom session for later playback?

Yes. Enable Agora Cloud Recording via their REST API. A Cloud Function triggers recording when the session starts and stops it when the session ends. The recorded file is saved to your cloud storage and the URL is stored on the session document.

How does the hand-raise queue work in real time?

Each hand-raise creates a Firestore document in the raised_hands subcollection. The instructor's ListView has Single Time Query set to OFF, so new entries appear instantly. The instructor can unmute the student via the Agora SDK and dismiss the hand-raise by deleting the document.

Can students share their screen during the session?

Yes. Agora supports screen sharing via the startScreenCapture method. Add a Screen Share button that calls this method when tapped. The screen share stream replaces the student's camera feed in the video grid.

What happens if a student loses internet connection during class?

Agora automatically attempts to reconnect. The attendance document retains the joinedAt timestamp. If reconnection fails, the student can rejoin, and a new attendance entry is created. The instructor sees the student disappear and reappear in the video grid.

Can RapidDev help build a complete virtual learning platform?

Yes. RapidDev can implement a full LMS with virtual classrooms, course management, grading, breakout rooms, recorded lectures, and student analytics dashboards.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.