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

How to Create a Mobile Esports Platform with Live Competitions in FlutterFlow

Build a mobile esports platform with Firestore collections for tournaments, matches, and teams. Create tournament brackets using a Custom Widget that renders an elimination tree from match data. Teams register for tournaments, both teams submit scores after each match, and an admin confirms results before advancing the bracket. Real-time Firestore listeners update scores live for spectators. A platform leaderboard ranks teams by wins across all tournaments.

What you'll learn

  • How to model tournament brackets and match data in Firestore
  • How to build a bracket visualization using a Custom Widget
  • How to implement a dual-submission score verification system
  • How to display live match scores with Firestore real-time listeners
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read30-40 minFlutterFlow Free+March 2026RapidDev Engineering Team
TL;DR

Build a mobile esports platform with Firestore collections for tournaments, matches, and teams. Create tournament brackets using a Custom Widget that renders an elimination tree from match data. Teams register for tournaments, both teams submit scores after each match, and an admin confirms results before advancing the bracket. Real-time Firestore listeners update scores live for spectators. A platform leaderboard ranks teams by wins across all tournaments.

Building a Mobile Esports Tournament Platform in FlutterFlow

Competitive mobile gaming needs structured tournament management with brackets, team rosters, score reporting, and live updates. This tutorial builds an esports platform where organizers create tournaments, teams register and manage rosters, matches follow elimination brackets with verified score submissions, and spectators watch live score updates.

Prerequisites

  • A FlutterFlow project with Firestore and authentication configured
  • Familiarity with FlutterFlow Custom Widgets (Dart code)
  • Basic understanding of single-elimination tournament bracket structure

Step-by-step guide

1

Design the Firestore data model for tournaments, matches, and teams

Create a tournaments collection with fields: name (String), game (String), format (String: 'single_elimination', 'round_robin', 'swiss'), maxTeams (Integer), startDate (Timestamp), prizePool (String), status (String: 'registration', 'in_progress', 'completed'), organizedBy (String). Under each tournament, add a matches subcollection: team1Id (String), team2Id (String), team1Name (String), team2Name (String), round (Integer), matchNumber (Integer), score1 (Integer, nullable), score2 (Integer, nullable), team1Submitted (Map: {score1, score2}), team2Submitted (Map: {score1, score2}), winnerId (String, nullable), status (String: 'scheduled', 'live', 'completed'), scheduledTime (Timestamp). Create a teams collection: name (String), captainId (String), memberIds (String Array), logoUrl (String), wins (Integer), losses (Integer).

Expected result: Firestore has tournaments with matches subcollection and a teams collection ready for bracket-based competitions.

2

Build the tournament bracket visualization as a Custom Widget

Create a Custom Widget named TournamentBracket that receives a list of match documents as a parameter. The widget renders a horizontal tree layout: Round 1 matches on the left, each pair of winners connecting to a Round 2 match to the right, and so on until the final. Each match node is a Container showing team1Name vs team2Name with scores. Highlight the winner in green and the loser in grey. Connect matches with painted lines using CustomPainter. For an 8-team single elimination bracket, you have 4 Round 1 matches, 2 Round 2 matches, and 1 final. The widget calculates vertical spacing to align match connectors properly.

tournament_bracket.dart
1// Custom Widget: TournamentBracket (simplified)
2import 'package:flutter/material.dart';
3
4class TournamentBracket extends StatelessWidget {
5 final List<Map<String, dynamic>> matches;
6 const TournamentBracket({super.key, required this.matches});
7
8 @override
9 Widget build(BuildContext context) {
10 final rounds = <int, List<Map<String, dynamic>>>{};
11 for (final m in matches) {
12 final r = m['round'] as int;
13 rounds.putIfAbsent(r, () => []).add(m);
14 }
15 final maxRound = rounds.keys.reduce(
16 (a, b) => a > b ? a : b);
17 return SingleChildScrollView(
18 scrollDirection: Axis.horizontal,
19 child: Row(
20 crossAxisAlignment: CrossAxisAlignment.center,
21 children: List.generate(maxRound, (i) {
22 final round = i + 1;
23 final roundMatches = rounds[round] ?? [];
24 roundMatches.sort((a, b) =>
25 (a['matchNumber'] as int)
26 .compareTo(b['matchNumber'] as int));
27 return Padding(
28 padding: const EdgeInsets.symmetric(
29 horizontal: 16),
30 child: Column(
31 mainAxisAlignment:
32 MainAxisAlignment.spaceEvenly,
33 children: roundMatches.map((m) {
34 return _MatchCard(match: m);
35 }).toList(),
36 ),
37 );
38 }),
39 ),
40 );
41 }
42}
43
44class _MatchCard extends StatelessWidget {
45 final Map<String, dynamic> match;
46 const _MatchCard({required this.match});
47
48 @override
49 Widget build(BuildContext context) {
50 final winner = match['winnerId'];
51 return Container(
52 width: 200, margin: const EdgeInsets.all(8),
53 decoration: BoxDecoration(
54 border: Border.all(color: Colors.grey),
55 borderRadius: BorderRadius.circular(8)),
56 child: Column(children: [
57 _teamRow(match['team1Name'],
58 match['score1'], match['team1Id'] == winner),
59 const Divider(height: 1),
60 _teamRow(match['team2Name'],
61 match['score2'], match['team2Id'] == winner),
62 ]),
63 );
64 }
65
66 Widget _teamRow(String? name, int? score, bool won) {
67 return Container(
68 color: won ? Colors.green.shade50 : null,
69 padding: const EdgeInsets.all(8),
70 child: Row(children: [
71 Expanded(child: Text(name ?? 'TBD',
72 style: TextStyle(
73 fontWeight: won ? FontWeight.bold
74 : FontWeight.normal))),
75 Text('${score ?? "-"}',
76 style: const TextStyle(fontSize: 16)),
77 ]),
78 );
79 }
80}

Expected result: A horizontally scrollable bracket widget displays all rounds with match cards showing teams, scores, and highlighted winners.

3

Implement team registration and roster management

Create a TeamManagementPage where the team captain manages their roster. Display the team name, logo (editable Image with upload), and a ListView of memberIds resolved to user names. Add an Invite Member button that searches users by name and adds their UID to memberIds. For tournament registration, on the TournamentDetailPage add a Register Team button visible when status is 'registration' and the team has not already registered. On tap, create a registration document and add the team to the tournament's registered teams. Show a registration count (e.g., 6/8 teams registered) with a progress indicator.

Expected result: Team captains manage rosters and register for tournaments. The tournament page shows registration progress toward the maximum team count.

4

Build the dual-submission score reporting system

On the MatchDetailPage, each team captain sees a Submit Score form with two number inputs (your score and opponent score). On submit, write the scores to team1Submitted or team2Submitted (based on which team the user captains) on the match document. After both teams submit, a Cloud Function compares the submissions. If they match, set score1, score2, winnerId, and status to 'completed'. If they conflict, set status to 'disputed' and notify the tournament admin. The admin sees a Dispute Resolution panel showing both submissions and can manually set the final score. This prevents any single team from fabricating results.

Expected result: Both teams submit scores independently. Matching scores auto-confirm. Conflicting scores flag a dispute for admin resolution.

5

Add real-time live score updates for spectators

On the TournamentBracketPage, set the Backend Query on matches to real-time (disable Single Time Query). When a match score is updated, the bracket widget rebuilds instantly showing new scores and advancing winners. Add a LiveMatchesPage that queries matches where status is 'live', showing a ListView of currently active matches with team names, current scores, and a pulsing red Live indicator. Spectators can tap any live match to see the MatchDetailPage with real-time score updates. Use a Container with a red dot and pulsing animation (Lottie or simple AnimatedOpacity) for the live indicator.

Expected result: Spectators see live score updates across all matches in real-time without refreshing. Active matches display a Live indicator.

6

Create the platform leaderboard ranking teams across tournaments

Build a LeaderboardPage with a ListView querying teams ordered by wins descending. Each row shows: rank number, team logo, team name, wins count, losses count, and win rate percentage. Add a TabBar to filter by game (All Games, Game A, Game B). For game-specific rankings, use a Cloud Function that tallies wins per game from completed matches and stores them in a team_game_stats subcollection. Highlight the top 3 teams with gold, silver, and bronze accent colors. Add a TournamentHistoryPage accessible from each team showing all tournaments they participated in with their finishing position.

Expected result: A global leaderboard ranks teams by wins with game-specific filtering and highlighted top-3 positions.

Complete working example

FlutterFlow Esports Platform Setup
1FIRESTORE DATA MODEL:
2 tournaments/{tournamentId}
3 name: String
4 game: String
5 format: 'single_elimination' | 'round_robin' | 'swiss'
6 maxTeams: Integer
7 startDate: Timestamp
8 prizePool: String
9 status: 'registration' | 'in_progress' | 'completed'
10 matches/{matchId}
11 team1Id: String
12 team2Id: String
13 team1Name: String
14 team2Name: String
15 round: Integer
16 matchNumber: Integer
17 score1: Integer (nullable)
18 score2: Integer (nullable)
19 team1Submitted: { score1: int, score2: int }
20 team2Submitted: { score1: int, score2: int }
21 winnerId: String (nullable)
22 status: 'scheduled' | 'live' | 'completed' | 'disputed'
23 scheduledTime: Timestamp
24
25 teams/{teamId}
26 name: String
27 captainId: String
28 memberIds: [String]
29 logoUrl: String
30 wins: Integer
31 losses: Integer
32
33PAGE: TournamentBracketPage (Route: tournamentId)
34 Column
35 Text (tournament name + game)
36 Row (status badge + teams count + prize pool)
37 Custom Widget: TournamentBracket
38 Backend Query: matches (real-time), all rounds
39 Renders horizontal tree: Round 1 Round 2 Final
40
41PAGE: MatchDetailPage (Route: matchId, tournamentId)
42 Column
43 Container (team1 vs team2 with logos)
44 Text (score1 : score2, large font)
45 Live indicator (if status == 'live')
46 Submit Score form (for team captains only)
47 Two number inputs + Submit button
48
49PAGE: LeaderboardPage
50 Column
51 TabBar (All Games | Game filters)
52 ListView (teams ordered by wins desc)
53 Row: rank + logo + name + wins + losses + win%
54
55SCORE VERIFICATION FLOW:
56 1. Team1 captain submits team1Submitted written
57 2. Team2 captain submits team2Submitted written
58 3. Cloud Function compares submissions
59 4. Match scores confirmed OR disputed
60 5. Winner advances to next round match

Common mistakes when creating a Mobile Esports Platform with Live Competitions in FlutterFlow

Why it's a problem: Auto-advancing the bracket based on a single team's score submission

How to avoid: Require both teams to submit matching scores. If scores conflict, flag the match as disputed for admin resolution before advancing.

Why it's a problem: Not using real-time queries for live match scores

How to avoid: Disable Single Time Query on all match-related Backend Queries so Firestore real-time listeners push score updates instantly.

Why it's a problem: Hardcoding bracket positions instead of calculating from match data

How to avoid: Calculate bracket layout dynamically from the round and matchNumber fields. The widget should handle any number of rounds automatically.

Best practices

  • Require dual score submission from both teams before confirming match results
  • Use real-time Firestore listeners for all match and bracket displays
  • Build the bracket widget to dynamically handle any number of rounds and teams
  • Add a dispute resolution flow for conflicting score submissions
  • Show a live indicator on active matches for spectator engagement
  • Maintain a global leaderboard ranking teams across all tournaments
  • Store team1Name and team2Name on match documents to avoid extra reads when rendering brackets

Still stuck?

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

ChatGPT Prompt

I want to build a mobile esports tournament platform in FlutterFlow with single-elimination brackets, team registration, dual score submission with dispute handling, live match updates, and a global leaderboard. Show me the Firestore data model, Custom Widget for bracket visualization, score verification Cloud Function, and page layouts.

FlutterFlow Prompt

Create a tournament detail page with the tournament name at top, a horizontal scrollable area in the middle for bracket display, and a list of upcoming matches at the bottom with team names and scheduled times.

Frequently asked questions

Can I support different tournament formats like round-robin or swiss?

Yes. The format field on tournaments supports multiple types. For round-robin, generate matches so every team plays every other team. For swiss, create matches round by round pairing teams with similar records. The bracket widget adapts to display different formats.

How do I generate the bracket matches automatically?

Create a Cloud Function triggered when tournament status changes to 'in_progress'. For single elimination with 8 teams, generate 4 Round 1 matches by seeding or random draw, 2 Round 2 match placeholders, and 1 final. Advance winners by updating the next round match documents.

Can spectators place predictions or bets on matches?

You can add a predictions feature where users select winners before matches start. Store predictions in a user_predictions subcollection and calculate accuracy scores. Actual betting requires gambling license compliance which varies by jurisdiction.

How do I handle teams that do not show up for their match?

Add a 'forfeit' status option for matches. If a team does not submit scores within a set time after the scheduled start, the admin can forfeit them, automatically advancing the opposing team.

Can I send push notifications for upcoming matches?

Yes. Use a scheduled Cloud Function that checks for matches starting in 15 minutes and sends push notifications to team members via Firebase Cloud Messaging. Also notify when scores are confirmed and brackets advance.

Can RapidDev help build a full esports tournament platform?

Yes. RapidDev can implement advanced bracket systems, anti-cheat integrations, streaming embeds, prize pool management, and team communication tools for a complete esports experience.

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.