Build a complete virtual learning environment in FlutterFlow that combines asynchronous course content with synchronous live classes. You will create a course module system with video lessons and reading materials, a quiz engine for assessments, a per-course discussion forum, a student dashboard with upcoming classes and assignment deadlines, course progress tracking with completion rings, and a gamification layer with XP points, badges, and a leaderboard. Start with an async course MVP and layer in live and interactive features incrementally.
Build a full virtual learning platform with courses, quizzes, forums, and gamification
This tutorial builds a virtual learning environment (VLE) that goes beyond a simple LMS. It combines asynchronous course content (video lessons, reading materials, assignments) with interactive features like quizzes, discussion forums, and gamification. Students see a dashboard with upcoming classes, assignment deadlines, and course progress rings. The gamification layer awards XP for completing lessons and badges for finishing courses, displayed on a leaderboard. The tutorial follows an incremental approach: you will build the async course engine first as a shippable MVP, then layer in quizzes, discussions, and gamification as separate additions.
Prerequisites
- A FlutterFlow project with Firebase/Firestore connected and Authentication enabled
- Firebase Storage for video and document uploads
- FlutterFlow Pro plan for Custom Widgets
- Basic understanding of Firestore subcollections and Backend Queries
Step-by-step guide
Design the Firestore data model for courses, lessons, quizzes, and progress
Design the Firestore data model for courses, lessons, quizzes, and progress
Create the following Firestore collections. courses: title (String), description (String), instructorId (String), thumbnailUrl (String), category (String), totalLessons (Integer), totalXP (Integer), isPublished (Boolean). courses/{id}/modules: title (String), order (Integer), type (String: video, reading, assignment). For video modules: videoUrl (String), durationMinutes (Integer). For reading modules: content (String, rich text). For assignment modules: instructions (String), dueDate (Timestamp). courses/{id}/quizzes: title (String), moduleId (String, optional — ties quiz to a module), questions (Array of Maps, each with questionText, type: multiple_choice/true_false/short_answer, options Array, correctAnswer String, points Integer). student_progress: userId (String), courseId (String), completedModuleIds (Array of Strings), quizScores (Map of quizId to score), totalXP (Integer), enrolledAt (Timestamp), lastAccessedAt (Timestamp). user_gamification: userId (String), totalXP (Integer), level (Integer), badges (Array of Maps with badgeName, earnedAt), streakDays (Integer). discussions: courseId (String), userId (String), userName (String), message (String), timestamp (Timestamp), parentId (String, null for top-level posts). Add sample data: 2 courses with 4-6 modules each, 1 quiz per course, and 2-3 discussion posts.
Expected result: All Firestore collections exist with sample data. Courses have modules, quizzes, and discussion threads. Composite indexes are created for progress and discussion queries.
Build the student dashboard with course progress rings and upcoming deadlines
Build the student dashboard with course progress rings and upcoming deadlines
Create a page called StudentDashboard. Add a Backend Query for student_progress where userId equals currentUserUid. At the top, display a greeting Row with 'Welcome back, {displayName}' and the user's current XP and level from user_gamification. Below, add a section titled 'My Courses' with a horizontal ListView (scrollDirection horizontal, height 180). Each item is a Container (width 160) with the course thumbnail Image, title Text, and a CircularPercentIndicator showing completion percentage. Calculate the percentage using a Custom Function: completedModuleIds.length / totalLessons. Set the progress color to green above 75%, amber 25-75%, red below 25%. On tap, navigate to the CourseViewer page. Below the courses, add a section titled 'Upcoming Deadlines'. Query all modules of type 'assignment' across enrolled courses where dueDate is greater than now, ordered by dueDate ascending, limited to 5. Display each as a Row with an Icon (Icons.assignment), the module title, course name, and a relative date (use the timeUntil Custom Function returning 'Tomorrow', 'In 3 days', etc.). At the bottom, add a section titled 'Continue Learning' showing the most recently accessed course with a prominent 'Continue' button that navigates directly to the last incomplete module.
Expected result: The dashboard shows enrolled courses with progress rings, upcoming assignment deadlines, and a continue-learning shortcut. XP and level display in the header.
Create the course module viewer with video lessons and completion tracking
Create the course module viewer with video lessons and completion tracking
Create a page called CourseViewer with a Page Parameter courseDocRef. Add a Backend Query for the course document and a second query for the modules subcollection ordered by the order field ascending. Display a left-side ListView of module titles as a table of contents (on tablet/desktop) or a top TabBar (on mobile). Each module row shows an Icon based on type (Icons.play_circle for video, Icons.article for reading, Icons.assignment for assignment), the title, and a Checkbox indicating completion (checked if the module ID exists in the student's completedModuleIds array). Tapping a module loads the content in the main area. For video modules: use a Custom Widget wrapping Chewie or video_player to play the videoUrl. Add a 'Mark as Complete' button below the video that adds the module ID to the completedModuleIds array in the student_progress document and awards XP (add 10 to totalXP in user_gamification). For reading modules: render the content in a Column with Text widgets using appropriate styling. For assignment modules: show the instructions and a TextField for submission with a Submit button. When a student completes the last module in a course, trigger a Custom Action that awards a course completion badge (add to the badges array in user_gamification) and shows a congratulatory Dialog.
Expected result: Students can navigate through course modules, watch videos, read content, and mark lessons complete. Completion is tracked in Firestore and reflected in the progress indicators.
Build the quiz engine with multiple question types and auto-grading
Build the quiz engine with multiple question types and auto-grading
Create a page called QuizPage with Page Parameters quizDocRef and courseId. Query the quiz document to get the title and questions array. Display the quiz title and question count at the top. Use a PageView widget to show one question per page with swipe navigation. For each question, display the questionText in titleMedium. Render different input widgets based on the type field: for multiple_choice, use RadioButton widgets for each option in the options array, bound to a Page State variable for the selected answer. For true_false, show two RadioButtons labeled True and False. For short_answer, show a TextField. Track all answers in a Page State variable answerMap (JSON map of question index to selected answer). Add Previous and Next buttons below the question for PageView navigation. On the last question, show a Submit Quiz button. The submit action iterates through answerMap, compares each answer against the correctAnswer field, tallies the score, and stores the result in the student_progress document's quizScores map (quizId to percentage score). Display results in a Dialog: total score, percentage, pass/fail (70% threshold), and a breakdown of correct/incorrect answers with explanations. Award XP based on score: full points for 90%+, half for 70-89%, none below 70%.
Expected result: Students take quizzes one question at a time with auto-grading on submission. Results show score breakdown and XP earned. Scores are saved to the student progress document.
Add the discussion forum with threaded replies
Add the discussion forum with threaded replies
Create a page called CourseDiscussion with a Page Parameter courseId. Add a Backend Query for the discussions collection where courseId matches and parentId is null (top-level posts), ordered by timestamp descending. Display posts in a ListView. Each post shows a CircleImage avatar, userName, the message text, a relative timestamp, and a Reply button. Tapping Reply opens a BottomSheet with a TextField and a Post button. The Post action creates a new discussions document with parentId set to the parent post's document ID. Below each top-level post, add a nested ListView querying discussions where parentId equals the post ID, ordered by timestamp ascending. Style replies with a left border (Container with left borderWidth 2, primary color) and slight left margin to visually indicate nesting. At the top of the page, add a TextField with a 'New Post' button for creating top-level discussion posts. Include the userName denormalized from the Auth profile to avoid extra queries on every post render. Add a report IconButton on each post for moderation.
Expected result: Students can create discussion posts, reply to existing posts in a threaded format, and see replies nested under parent posts with visual indentation.
Implement gamification with XP, badges, and a leaderboard
Implement gamification with XP, badges, and a leaderboard
Create a page called Leaderboard. Query user_gamification ordered by totalXP descending, limited to 50. Display a ListView where each item shows the rank number, user avatar (CircleImage from user profile), display name, totalXP with a trophy icon, level, and badge count. Highlight the current user's row with a distinct background color (use Conditional Styles where userId equals currentUserUid). At the top, show the current user's stats in a prominent card: total XP, current level, streak days, and a GridView of earned badges (each badge as an Icon with a label). Badges are awarded by the Custom Action triggered at specific milestones: First Lesson (complete 1 module), Course Complete (finish all modules in a course), Quiz Master (score 90%+ on 3 quizzes), Discussion Starter (create 5 forum posts), Week Streak (access the app 7 consecutive days). Level is calculated from totalXP: Level 1 at 0 XP, Level 2 at 100 XP, Level 3 at 300 XP, and so on with increasing thresholds stored in an App State constant list. Display a LinearProgressIndicator showing progress toward the next level. When a new badge is earned, show an animated Dialog with the badge icon, name, and a congratulatory message.
Expected result: A leaderboard ranks students by XP. Each student sees their badges, level, streak, and progress toward the next level. Badge award animations celebrate milestones.
Complete working example
1Firestore Data Model:2├── courses/{auto-id}3│ ├── title, description, instructorId, thumbnailUrl4│ ├── category, totalLessons, totalXP, isPublished5│ ├── modules/{auto-id} (subcollection)6│ │ ├── title, order, type (video | reading | assignment)7│ │ ├── videoUrl, durationMinutes (video type)8│ │ ├── content (reading type)9│ │ └── instructions, dueDate (assignment type)10│ ├── quizzes/{auto-id} (subcollection)11│ │ ├── title, moduleId (optional)12│ │ └── questions: Array of Maps13│ │ ├── questionText, type, options, correctAnswer, points14│ └── (discussions are top-level, linked by courseId)15├── student_progress/{auto-id}16│ ├── userId, courseId, completedModuleIds (Array)17│ ├── quizScores (Map: quizId → percentage)18│ ├── totalXP, enrolledAt, lastAccessedAt19├── user_gamification/{userId}20│ ├── totalXP, level, streakDays21│ ├── badges: Array of {badgeName, earnedAt}22│ └── lastActiveDate: Timestamp23└── discussions/{auto-id}24 ├── courseId, userId, userName, message25 ├── timestamp, parentId (null = top-level)2627Custom Functions:28 calcCompletionPercent(completed, total) → double29 timeUntil(date) → string (Tomorrow, In 3 days)30 calcLevel(xp) → int level from XP thresholds3132Page: StudentDashboard33├── Header: Welcome + XP + Level34├── My Courses: horizontal ListView35│ └── Card: thumbnail + title + CircularPercentIndicator36├── Upcoming Deadlines: ListView (5 nearest assignments)37│ └── Row: icon + title + course + relative date38└── Continue Learning: last accessed course + Continue button3940Page: CourseViewer (param: courseDocRef)41├── Module list (sidebar or TabBar)42│ └── Icon (type) + title + completion Checkbox43├── Content area:44│ ├── Video: Custom Widget (video_player) + Mark Complete45│ ├── Reading: styled Text content + Mark Complete46│ └── Assignment: instructions + TextField + Submit47└── On last module complete → award badge + Dialog4849Page: QuizPage (params: quizDocRef, courseId)50├── PageView (one question per page)51│ ├── multiple_choice: RadioButtons52│ ├── true_false: RadioButtons (True/False)53│ └── short_answer: TextField54├── Navigation: Previous + Next buttons55└── Submit → auto-grade → score Dialog → award XP5657Page: CourseDiscussion (param: courseId)58├── New Post: TextField + Post button59├── ListView (top-level posts, parentId == null)60│ └── Post: avatar + name + message + timestamp + Reply61│ └── Nested ListView (replies, parentId == postId)62│ └── Reply: left border indent + content6364Page: Leaderboard65├── Current User Card: XP + level + streak + badge grid66├── Progress: LinearProgressIndicator to next level67└── ListView (top 50 by XP)68 └── Row: rank + avatar + name + XP + level + badgesCommon mistakes
Why it's a problem: Building every VLE feature at once before launching
How to avoid: Start with async courses (modules + video + completion tracking) as a shippable MVP. Launch it. Then add quizzes, then discussions, then gamification in separate iterations. Each addition is a standalone improvement that users can start using immediately.
Why it's a problem: Storing quiz answers as an array in the quiz document instead of using Page State
How to avoid: Collect all answers in a Page State variable (answerMap). Only write to Firestore once on final submission. This reduces writes to a single batch and lets students freely change answers before submitting.
Why it's a problem: Not ordering modules by the order field in queries
How to avoid: Always query the modules subcollection with orderBy order ascending. Use integer values (1, 2, 3) for the order field and leave gaps (10, 20, 30) to allow inserting new modules between existing ones without reordering.
Best practices
- Ship the async course engine first as an MVP before adding quizzes, forums, or gamification
- Use subcollections for modules and quizzes under each course to keep data organized and queries scoped
- Always order modules by the order field ascending to ensure lessons display in the correct sequence
- Collect all quiz answers in Page State and write to Firestore only once on submission to minimize write costs
- Denormalize userName into discussion posts to avoid a user profile lookup on every post render
- Use CircularPercentIndicator for course progress — it gives visual feedback at a glance on the dashboard
- Track streakDays by comparing lastActiveDate to yesterday, not by running a scheduled function
- Leave gaps in module order values (10, 20, 30) so instructors can insert new modules without reordering everything
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Design a Firestore data model for a virtual learning environment. I need collections for courses with a modules subcollection (video, reading, assignment types), quizzes with multiple question types (multiple_choice, true_false, short_answer), student_progress tracking completed modules and quiz scores, user_gamification with XP, levels, badges, and streaks, and a discussions collection with threaded replies via parentId. Include the relationships and suggest indexes.
Create a student dashboard page with a horizontal scrolling list of enrolled courses. Each course card shows a thumbnail, title, and a circular progress indicator showing completion percentage. Below the courses, add an upcoming deadlines section listing assignment names with due dates. Add a leaderboard page with a ranked list showing student name, XP points, level, and badge count, with the current user's row highlighted.
Frequently asked questions
How do I add live video classes to the VLE?
Integrate a video conferencing service like Agora, Twilio, or Daily.co via a Custom Widget that embeds their Flutter SDK. Create a scheduled_classes collection with courseId, instructorId, startTime, and meetingUrl. The student dashboard queries upcoming classes and shows a Join button that opens the video widget when the class starts. Start with async content first and add live classes as a second phase.
Can instructors create courses within FlutterFlow?
Yes. Build an instructor admin page gated by a user role field. The page has forms for creating course documents, adding modules with file uploads (video to Firebase Storage, reading content in a rich text field), creating quizzes with a dynamic form for adding questions, and publishing/unpublishing courses. Use the same Firestore collections — instructors write, students read.
How does the XP and leveling system work?
Students earn XP for actions: 10 XP per module completed, 20 XP per quiz passed, 5 XP per discussion post. XP thresholds define levels: Level 1 at 0 XP, Level 2 at 100, Level 3 at 300, Level 4 at 600, and so on with increasing gaps. Store thresholds in an App State constant list. A Custom Function takes current XP and returns the level by iterating through thresholds.
How do I prevent students from skipping to the final quiz?
Add a prerequisite check before allowing quiz access. On the QuizPage, query the student_progress document and verify that completedModuleIds contains all module IDs that precede the quiz. If not, show a message listing the incomplete modules and a button to navigate back to the course viewer. Enforce this in Firestore Security Rules as well.
What if my course has hundreds of students — will Firestore handle it?
Yes. Each student has their own student_progress document, so progress tracking scales linearly. The leaderboard query (top 50 by XP) is a single indexed query regardless of total students. Discussion posts are the main scaling concern — add pagination with limit and startAfter for courses with many posts. Firestore handles millions of documents per collection without issue.
Can RapidDev help build a production virtual learning environment?
Yes. A production VLE typically needs video streaming infrastructure, live class scheduling with calendar integration, SCORM/xAPI compliance for content interoperability, certificate generation, payment integration for paid courses, and analytics dashboards for instructors. RapidDev can architect and build the complete platform beyond what FlutterFlow's visual builder handles alone.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation