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

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 30-40 min
- Compatibility: FlutterFlow Free+
- Last updated: March 2026

## 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.

## Before you start

- 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.

```
// Custom Widget: TournamentBracket (simplified)
import 'package:flutter/material.dart';

class TournamentBracket extends StatelessWidget {
  final List<Map<String, dynamic>> matches;
  const TournamentBracket({super.key, required this.matches});

  @override
  Widget build(BuildContext context) {
    final rounds = <int, List<Map<String, dynamic>>>{};
    for (final m in matches) {
      final r = m['round'] as int;
      rounds.putIfAbsent(r, () => []).add(m);
    }
    final maxRound = rounds.keys.reduce(
      (a, b) => a > b ? a : b);
    return SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: List.generate(maxRound, (i) {
          final round = i + 1;
          final roundMatches = rounds[round] ?? [];
          roundMatches.sort((a, b) =>
            (a['matchNumber'] as int)
              .compareTo(b['matchNumber'] as int));
          return Padding(
            padding: const EdgeInsets.symmetric(
              horizontal: 16),
            child: Column(
              mainAxisAlignment:
                MainAxisAlignment.spaceEvenly,
              children: roundMatches.map((m) {
                return _MatchCard(match: m);
              }).toList(),
            ),
          );
        }),
      ),
    );
  }
}

class _MatchCard extends StatelessWidget {
  final Map<String, dynamic> match;
  const _MatchCard({required this.match});

  @override
  Widget build(BuildContext context) {
    final winner = match['winnerId'];
    return Container(
      width: 200, margin: const EdgeInsets.all(8),
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey),
        borderRadius: BorderRadius.circular(8)),
      child: Column(children: [
        _teamRow(match['team1Name'],
          match['score1'], match['team1Id'] == winner),
        const Divider(height: 1),
        _teamRow(match['team2Name'],
          match['score2'], match['team2Id'] == winner),
      ]),
    );
  }

  Widget _teamRow(String? name, int? score, bool won) {
    return Container(
      color: won ? Colors.green.shade50 : null,
      padding: const EdgeInsets.all(8),
      child: Row(children: [
        Expanded(child: Text(name ?? 'TBD',
          style: TextStyle(
            fontWeight: won ? FontWeight.bold
              : FontWeight.normal))),
        Text('${score ?? "-"}',
          style: const TextStyle(fontSize: 16)),
      ]),
    );
  }
}
```

**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 code example

File: `FlutterFlow Esports Platform Setup`

```text
FIRESTORE DATA MODEL:
  tournaments/{tournamentId}
    name: String
    game: String
    format: 'single_elimination' | 'round_robin' | 'swiss'
    maxTeams: Integer
    startDate: Timestamp
    prizePool: String
    status: 'registration' | 'in_progress' | 'completed'
    └── matches/{matchId}
          team1Id: String
          team2Id: String
          team1Name: String
          team2Name: String
          round: Integer
          matchNumber: Integer
          score1: Integer (nullable)
          score2: Integer (nullable)
          team1Submitted: { score1: int, score2: int }
          team2Submitted: { score1: int, score2: int }
          winnerId: String (nullable)
          status: 'scheduled' | 'live' | 'completed' | 'disputed'
          scheduledTime: Timestamp

  teams/{teamId}
    name: String
    captainId: String
    memberIds: [String]
    logoUrl: String
    wins: Integer
    losses: Integer

PAGE: TournamentBracketPage (Route: tournamentId)
  Column
    ├── Text (tournament name + game)
    ├── Row (status badge + teams count + prize pool)
    └── Custom Widget: TournamentBracket
          Backend Query: matches (real-time), all rounds
          Renders horizontal tree: Round 1 → Round 2 → Final

PAGE: MatchDetailPage (Route: matchId, tournamentId)
  Column
    ├── Container (team1 vs team2 with logos)
    ├── Text (score1 : score2, large font)
    ├── Live indicator (if status == 'live')
    └── Submit Score form (for team captains only)
          Two number inputs + Submit button

PAGE: LeaderboardPage
  Column
    ├── TabBar (All Games | Game filters)
    └── ListView (teams ordered by wins desc)
          Row: rank + logo + name + wins + losses + win%

SCORE VERIFICATION FLOW:
  1. Team1 captain submits → team1Submitted written
  2. Team2 captain submits → team2Submitted written
  3. Cloud Function compares submissions
  4. Match → scores confirmed OR disputed
  5. Winner advances to next round match
```

## Common mistakes

- **Auto-advancing the bracket based on a single team's score submission** — The losing team might dispute the result. Accepting one team's submission without verification allows score fabrication and unfair bracket advancement. Fix: Require both teams to submit matching scores. If scores conflict, flag the match as disputed for admin resolution before advancing.
- **Not using real-time queries for live match scores** — Spectators see stale scores and must manually refresh to see updates. This defeats the purpose of live competition viewing. Fix: Disable Single Time Query on all match-related Backend Queries so Firestore real-time listeners push score updates instantly.
- **Hardcoding bracket positions instead of calculating from match data** — Any bracket size change (8 teams vs 16 teams) breaks the layout. Adding or removing rounds requires rewriting the entire widget. Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-platform-for-mobile-esports-with-live-competitions-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-platform-for-mobile-esports-with-live-competitions-in-flutterflow
