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

How to Create a Sports Scores and Live Updates Feature in FlutterFlow

Live sports scores in FlutterFlow work by running a single Cloud Function on a 60-second schedule that polls a sports API and writes updated scores to Firestore. The FlutterFlow app subscribes to those Firestore documents via real-time Backend Queries. For score change alerts, the same Cloud Function triggers FCM push notifications to users who favorited the relevant teams. Never poll the sports API from each client device.

What you'll learn

  • How to set up a scheduled Cloud Function that polls a sports API every 60 seconds
  • How to display live scores using Firestore real-time Backend Queries in FlutterFlow
  • How to send FCM push notifications when a favorite team scores
  • How to build a schedule, standings, and favorites UI in FlutterFlow
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner11 min read45-60 minFlutterFlow Free+ (Cloud Functions and FCM require Firebase Blaze plan)March 2026RapidDev Engineering Team
TL;DR

Live sports scores in FlutterFlow work by running a single Cloud Function on a 60-second schedule that polls a sports API and writes updated scores to Firestore. The FlutterFlow app subscribes to those Firestore documents via real-time Backend Queries. For score change alerts, the same Cloud Function triggers FCM push notifications to users who favorited the relevant teams. Never poll the sports API from each client device.

Live Sports Data via Server-Side Polling and Firestore Fan-out

Sports apps need data that changes every few minutes during live games — goals, fouls, quarter scores, and game state. The architecture that scales is simple: one Cloud Function polls the sports API (The Sports DB, API-SPORTS, or a similar provider) on a schedule, writes the latest data to Firestore, and lets Firestore distribute updates to all connected clients via its real-time listeners. This means a single API call serves thousands of users, and each client pays zero additional API cost regardless of how many viewers watch the same game.

Prerequisites

  • A Firebase project with Firestore, Cloud Functions, and Cloud Messaging enabled (Blaze plan)
  • A FlutterFlow project connected to that Firebase project
  • An API key from a sports data provider (The Sports DB free tier works for demos, API-SPORTS for production)
  • Basic familiarity with FlutterFlow Backend Queries and Action Flows

Step-by-step guide

1

Design the Firestore schema for games, teams, and standings

Create four Firestore collections. The 'games' collection stores each match with fields: id, home_team_id, away_team_id, home_score (Integer), away_score (Integer), status (String: scheduled/live/final), start_time (Timestamp), league_id, sport, and last_updated (Timestamp). The 'teams' collection stores team metadata: id, name, logo_url, sport, league_id, wins, losses, draws. The 'standings' collection stores current league standings as a ranked array per league document. The 'user_favorites' collection stores per-user team preferences: one document per user (keyed by UID) with a team_ids Array field. In FlutterFlow, import all four collections via the Firestore panel.

Expected result: All four collections appear in FlutterFlow's Firestore panel. Manually create one test game document to verify field types are mapped correctly.

2

Deploy the scheduled polling Cloud Function

Create a Cloud Function called 'pollSportsScores' using the Firebase Cloud Functions PubSub scheduler, set to run every 1 minute. The function calls your sports API for live games (any game with status 'live' or scheduled to start within 15 minutes), parses the response, and does a batch write to Firestore updating all changed game documents. Include error handling with exponential backoff for API rate limit responses (HTTP 429). Log the number of games updated each run so you can monitor in Cloud Functions logs. Deploy using firebase deploy --only functions.

functions/pollSportsScores.js
1// functions/pollSportsScores.js
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4const axios = require('axios');
5
6if (!admin.apps.length) admin.initializeApp();
7const db = admin.firestore();
8
9exports.pollSportsScores = functions.pubsub
10 .schedule('every 1 minutes')
11 .onRun(async () => {
12 const apiKey = functions.config().sports.api_key;
13 const baseUrl = 'https://v3.football.api-sports.io';
14 const today = new Date().toISOString().split('T')[0];
15
16 let response;
17 try {
18 response = await axios.get(`${baseUrl}/fixtures?date=${today}&status=1H-2H-HT-ET-BT`, {
19 headers: { 'x-apisports-key': apiKey },
20 timeout: 8000
21 });
22 } catch (err) {
23 console.error('Sports API error:', err.message);
24 return null;
25 }
26
27 const fixtures = response.data?.response || [];
28 if (!fixtures.length) { console.log('No live fixtures'); return null; }
29
30 // Read current Firestore scores to detect changes
31 const batch = db.batch();
32 const changeNotifications = [];
33
34 for (const fixture of fixtures) {
35 const gameRef = db.collection('games').doc(String(fixture.fixture.id));
36 const existingSnap = await gameRef.get();
37 const existing = existingSnap.data();
38
39 const homeScore = fixture.goals.home || 0;
40 const awayScore = fixture.goals.away || 0;
41
42 // Detect score change for push notification
43 if (existing && (existing.home_score !== homeScore || existing.away_score !== awayScore)) {
44 changeNotifications.push({
45 home_team_id: String(fixture.teams.home.id),
46 away_team_id: String(fixture.teams.away.id),
47 home_score: homeScore,
48 away_score: awayScore,
49 home_name: fixture.teams.home.name,
50 away_name: fixture.teams.away.name,
51 });
52 }
53
54 batch.set(gameRef, {
55 id: String(fixture.fixture.id),
56 home_team_id: String(fixture.teams.home.id),
57 away_team_id: String(fixture.teams.away.id),
58 home_team_name: fixture.teams.home.name,
59 away_team_name: fixture.teams.away.name,
60 home_team_logo: fixture.teams.home.logo,
61 away_team_logo: fixture.teams.away.logo,
62 home_score: homeScore,
63 away_score: awayScore,
64 status: fixture.fixture.status.short,
65 elapsed: fixture.fixture.status.elapsed,
66 last_updated: admin.firestore.FieldValue.serverTimestamp()
67 }, { merge: true });
68 }
69
70 await batch.commit();
71 console.log(`Updated ${fixtures.length} live games, ${changeNotifications.length} score changes`);
72
73 // Send push notifications for score changes
74 for (const change of changeNotifications) {
75 await sendScoreNotification(change);
76 }
77 return null;
78 });
79
80async function sendScoreNotification(change) {
81 // Find users who favorited either team
82 const favSnap = await db.collection('user_favorites')
83 .where('team_ids', 'array-contains-any',
84 [change.home_team_id, change.away_team_id])
85 .get();
86 if (favSnap.empty) return;
87
88 const tokens = [];
89 favSnap.docs.forEach(doc => {
90 const token = doc.data().fcm_token;
91 if (token) tokens.push(token);
92 });
93 if (!tokens.length) return;
94
95 await admin.messaging().sendMulticast({
96 tokens,
97 notification: {
98 title: `${change.home_name} ${change.home_score} - ${change.away_score} ${change.away_name}`,
99 body: 'Score update'
100 },
101 data: { type: 'score_update' }
102 });
103}

Expected result: The Cloud Function runs every minute. Check Cloud Functions logs in Firebase console — you should see 'Updated X live games' entries. Check Firestore to confirm game documents are being created and updated.

3

Build the live scores screen with real-time Backend Queries

In FlutterFlow, create a 'LiveScoresPage'. Add a Backend Query at the page level that queries the 'games' collection filtered by status equal to 'live', ordered by start_time, with real-time listening enabled. Below a 'LIVE' header with a pulsing red dot animation (created with an Animated Container cycling between full and half opacity), add a ListView bound to this query. Each list item is a 'ScoreCard' Component showing both team logos side-by-side, both team names, the current score in a large bold font, the game elapsed time (e.g., '67'''), and a status badge. Add a second ListView for 'Today's Games' filtered by today's date and status not equal to 'live', to show upcoming and completed fixtures.

Expected result: The live scores page shows real-time updating scores. When you manually change a score value in Firestore, the UI updates within 1-2 seconds without any page refresh.

4

Add a favorite teams feature with FCM notifications

Create a 'TeamsPage' that lists all teams from the 'teams' Firestore collection. Each team row has a star/heart icon button. On tap, the action flow checks if the team ID is already in the user's user_favorites document: if yes, remove it; if no, add it. Also store the user's FCM device token in the user_favorites document (fetch it via a Custom Action using FirebaseMessaging.instance.getToken()). When the pollSportsScores Cloud Function detects a score change, it queries user_favorites where team_ids array-contains the team ID and sends an FCM push notification to those users. In FlutterFlow, add a Custom Action in your app's initState to request notification permissions and save the FCM token.

custom_actions/save_fcm_token.dart
1// custom_actions/save_fcm_token.dart
2import 'package:firebase_messaging/firebase_messaging.dart';
3import 'package:cloud_firestore/cloud_firestore.dart';
4import 'package:firebase_auth/firebase_auth.dart';
5
6Future<void> saveFcmToken() async {
7 final messaging = FirebaseMessaging.instance;
8 final settings = await messaging.requestPermission();
9 if (settings.authorizationStatus != AuthorizationStatus.authorized) return;
10 final token = await messaging.getToken();
11 if (token == null) return;
12 final uid = FirebaseAuth.instance.currentUser?.uid;
13 if (uid == null) return;
14 await FirebaseFirestore.instance
15 .collection('user_favorites')
16 .doc(uid)
17 .set({'fcm_token': token}, SetOptions(merge: true));
18}

Expected result: After enabling notifications, the user's FCM token appears in their user_favorites document. When a manually-triggered score change occurs in Firestore, a push notification arrives on the user's device within 30-60 seconds.

5

Build a standings and schedule tab UI

Add a TabBar at the top of your sports page with three tabs: 'Live', 'Schedule', and 'Standings'. The Schedule tab shows a ListView of all today's games plus the next 7 days, filtered by status 'scheduled'. Add a DatePicker row at the top of the schedule so users can browse past results. The Standings tab reads from the 'standings' Firestore collection, which is updated by a second scheduled Cloud Function that runs once per day. Display standings as a table with columns: Position, Team (with logo), Played, Won, Drawn, Lost, Points. Add a league selector at the top (a horizontal ScrollView of league logo chips) to switch between competitions.

Expected result: All three tabs work independently. Live tab shows real-time scores, Schedule tab shows upcoming fixtures, and Standings tab shows the current league table.

Complete working example

functions/sportsUpdate.js
1// sportsUpdate.js — Complete sports data pipeline
2// Polls API every minute for live scores
3// Updates standings once per day
4// Sends FCM push notifications on score changes
5
6const functions = require('firebase-functions');
7const admin = require('firebase-admin');
8const axios = require('axios');
9
10if (!admin.apps.length) admin.initializeApp();
11const db = admin.firestore();
12
13const API_KEY = () => functions.config().sports?.api_key;
14const BASE_URL = 'https://v3.football.api-sports.io';
15const HEADERS = () => ({ 'x-apisports-key': API_KEY(), 'Content-Type': 'application/json' });
16
17async function fetchWithRetry(url, retries = 2) {
18 for (let i = 0; i <= retries; i++) {
19 try {
20 return await axios.get(url, { headers: HEADERS(), timeout: 8000 });
21 } catch (e) {
22 if (e.response?.status === 429 && i < retries) {
23 await new Promise(r => setTimeout(r, 2000 * (i + 1)));
24 } else throw e;
25 }
26 }
27}
28
29exports.pollLiveScores = functions.pubsub
30 .schedule('every 1 minutes').onRun(async () => {
31 const today = new Date().toISOString().split('T')[0];
32 let res;
33 try {
34 res = await fetchWithRetry(`${BASE_URL}/fixtures?date=${today}&live=all`);
35 } catch (e) {
36 console.error('API fetch failed:', e.message);
37 return null;
38 }
39 const fixtures = res.data?.response || [];
40 if (!fixtures.length) return null;
41
42 const scoreChanges = [];
43 const batch = db.batch();
44
45 for (const f of fixtures) {
46 const gameId = String(f.fixture.id);
47 const ref = db.collection('games').doc(gameId);
48 const snap = await ref.get();
49 const prev = snap.data();
50 const homeScore = f.goals.home ?? 0;
51 const awayScore = f.goals.away ?? 0;
52
53 if (prev && (prev.home_score !== homeScore || prev.away_score !== awayScore)) {
54 scoreChanges.push({
55 home_team_id: String(f.teams.home.id),
56 away_team_id: String(f.teams.away.id),
57 home_name: f.teams.home.name,
58 away_name: f.teams.away.name,
59 home_score: homeScore,
60 away_score: awayScore,
61 });
62 }
63
64 batch.set(ref, {
65 home_team_name: f.teams.home.name,
66 away_team_name: f.teams.away.name,
67 home_team_logo: f.teams.home.logo,
68 away_team_logo: f.teams.away.logo,
69 home_team_id: String(f.teams.home.id),
70 away_team_id: String(f.teams.away.id),
71 home_score: homeScore,
72 away_score: awayScore,
73 status: f.fixture.status.short,
74 elapsed: f.fixture.status.elapsed,
75 league_name: f.league.name,
76 league_logo: f.league.logo,
77 last_updated: admin.firestore.FieldValue.serverTimestamp(),
78 }, { merge: true });
79 }
80
81 await batch.commit();
82 await Promise.all(scoreChanges.map(notifyFavoriteUsers));
83 console.log(`Synced ${fixtures.length} games, ${scoreChanges.length} score changes`);
84 return null;
85 });
86
87async function notifyFavoriteUsers(change) {
88 const snap = await db.collection('user_favorites')
89 .where('team_ids', 'array-contains-any',
90 [change.home_team_id, change.away_team_id]).limit(500).get();
91 if (snap.empty) return;
92 const tokens = snap.docs.map(d => d.data().fcm_token).filter(Boolean);
93 if (!tokens.length) return;
94 const chunks = [];
95 for (let i = 0; i < tokens.length; i += 500) chunks.push(tokens.slice(i, i + 500));
96 await Promise.all(chunks.map(chunk =>
97 admin.messaging().sendMulticast({
98 tokens: chunk,
99 notification: {
100 title: `${change.home_name} ${change.home_score}-${change.away_score} ${change.away_name}`,
101 body: 'Score update from your favorite match'
102 },
103 data: { type: 'score_update', home_team_id: change.home_team_id }
104 })
105 ));
106}

Common mistakes when creating a Sports Scores and Live Updates Feature in FlutterFlow

Why it's a problem: Polling the sports API from every client device individually

How to avoid: Use a single scheduled Cloud Function as the sole API caller. All clients read from Firestore, which is designed to serve the same data to unlimited concurrent readers cheaply.

Why it's a problem: Sending a push notification for every Firestore write regardless of change

How to avoid: Compare the new score with the previous Firestore value before sending. Only trigger FCM when home_score or away_score has actually changed.

Why it's a problem: Forgetting to request notification permissions before saving the FCM token

How to avoid: Always await requestPermission() first, check that authorizationStatus is 'authorized', then call getToken(). Implement this in the app's first-launch flow with a clear explanation of why notifications are useful.

Best practices

  • Use a single scheduled Cloud Function as the sole sports API caller — fan out to all clients via Firestore real-time listeners.
  • Store your sports API key in Firebase Functions config (firebase functions:config:set sports.api_key=YOUR_KEY) and never in client-side code.
  • Mark game documents as 'final' when the game ends and stop updating them to reduce unnecessary Firestore writes.
  • Add a 'last_api_call' timestamp document to track polling health — if it's more than 3 minutes old, your scheduled function may have stalled.
  • Implement FCM token refresh handling: listen to FirebaseMessaging.onTokenRefresh and update the stored token in Firestore.
  • Cap FCM notifications to a maximum of 3 per game per user to avoid notification fatigue during high-scoring matches.
  • Cache team logos in Flutter's image cache — they don't change often but are loaded frequently. Use CachedNetworkImage package after code export for automatic disk caching.

Still stuck?

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

ChatGPT Prompt

I'm building a Firebase Cloud Function that polls the API-Sports football API every minute to get live match scores. Write a Node.js function that: fetches all live fixtures for today, compares the new scores with values stored in a Firestore 'games' collection, performs a batch update to Firestore with the latest scores, and returns a list of score change events. Include error handling for API rate limit responses (HTTP 429) with exponential backoff retry.

FlutterFlow Prompt

In my FlutterFlow project I have a 'games' Firestore collection with fields: home_team_name, away_team_name, home_score, away_score, status, and elapsed. I want to display live games in a ListView that updates in real-time as scores change. Walk me through setting up a Backend Query on the games collection filtered to only show documents where status is 'live', with real-time listening enabled, and binding the score fields to Text widgets in a ScoreCard component.

Frequently asked questions

Which sports data API should I use?

The Sports DB is free for non-commercial use with historical data. API-Sports (api-sports.io) offers live data from $14/month and covers football, basketball, baseball, hockey, and more. Sportradar and Stats Perform are enterprise-tier options for production apps with SLA guarantees.

How do I handle time zones for game schedules?

Store all timestamps in Firestore as UTC. When displaying to users, use FlutterFlow's DateTime formatting with the device's local timezone, or include a timezone selector for users in multiple regions.

Can I show in-game events like goals and yellow cards, not just scores?

Yes. Most sports APIs return event arrays per fixture (goal at minute 23, yellow card at minute 45). Extend your game document schema with an 'events' Array field and update it in the polling function. Display events as a scrollable timeline below the score in the game detail view.

What happens when the Cloud Function fails and scores stop updating?

Add a health check document in Firestore (system/polling_health) that the function updates with a timestamp on each run. Display a 'scores may be delayed' banner in the app if last_updated is more than 3 minutes ago.

How do I add live commentary or play-by-play text?

Use a sports API endpoint that provides commentary events (most premium APIs include this). Store commentary as a sub-collection under each game document and display it as a real-time updating ListView — new entries appear at the top as the function writes them.

Can I monetize the sports feature with ads around live scores?

Yes. Google AdMob integrates with FlutterFlow via Custom Widgets after code export. Place banner ads below the live scores list and interstitial ads between navigating from the scores list to a game detail view. Follow sports data provider terms of service — many prohibit commercial use on free API tiers.

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.