Documentation Index Fetch the complete documentation index at: https://docs.fieldfunded.com/llms.txt
Use this file to discover all available pages before exploring further.
Build a Mobile Sports Odds App with React Native
Ship a native iOS and Android odds app from a single codebase. This guide connects React Native to the FieldFunded API for live odds, real-time scores, and automatic bet settlement — all optimized for mobile data usage and battery life.
Why Mobile Needs a Different Approach
Mobile apps have constraints that web dashboards do not:
Constraint Impact Solution Battery drain Frequent polling kills battery Adaptive polling intervals Cellular data Users on metered connections Request compression + caching Screen size Less space for market tables Collapsed market groups Background limits iOS/Android kill background tasks Push notifications instead of polling
What You’ll Use
SDK Method Endpoint Purpose getEvents()GET /v1/eventsList upcoming matches getEventOdds()GET /v1/events/{id}/oddsGet odds for a match getLive()GET /v1/liveCurrently in-play events getScores()GET /v1/scoresLive scores checkBet()POST /v1/bets/checkSettle bets
Step 1: Set Up the API Client
Create a lightweight client optimized for mobile:
// api/fieldfunded.ts
const BASE_URL = 'https://api.fieldfunded.com/v1' ;
const API_KEY = process . env . FIELDFUNDED_API_KEY ! ;
const headers = {
'X-API-Key' : API_KEY ,
'Accept-Encoding' : 'gzip' , // Reduce data usage
};
export async function apiGet < T >( path : string , params ?: Record < string , string >) : Promise < T > {
const url = new URL ( ` ${ BASE_URL }${ path } ` );
if ( params ) {
Object . entries ( params ). forEach (([ k , v ]) => url . searchParams . set ( k , v ));
}
const res = await fetch ( url . toString (), { headers });
if ( ! res . ok ) throw new Error ( `API error: ${ res . status } ` );
return res . json ();
}
Step 2: Build the Events List Screen
// screens/EventsScreen.tsx
import React , { useEffect , useState } from 'react' ;
import { FlatList , Text , View , TouchableOpacity , StyleSheet } from 'react-native' ;
import { apiGet } from '../api/fieldfunded' ;
interface Event {
id : string ;
home_team : string ;
away_team : string ;
commence_time : string ;
sport_key : string ;
}
export function EventsScreen ({ navigation } : any ) {
const [ events , setEvents ] = useState < Event []>([]);
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
apiGet <{ events : Event [] }>( '/events' , { sport: 'soccer' })
. then ( data => setEvents ( data . events || []))
. finally (() => setLoading ( false ));
}, []);
return (
< FlatList
data = { events }
keyExtractor = { item => item . id }
renderItem = {({ item }) => (
< TouchableOpacity
style = {styles. card }
onPress = {() => navigation.navigate( 'Odds' , { eventId : item . id })}
>
<Text style = {styles. teams } >
{ item . home_team } vs { item . away_team }
</Text>
<Text style = {styles. time } >
{ new Date ( item . commence_time ). toLocaleString ()}
</Text>
</TouchableOpacity>
)}
/>
);
}
const styles = StyleSheet . create ({
card: { padding: 16 , borderBottomWidth: 1 , borderColor: '#eee' },
teams: { fontSize: 16 , fontWeight: '600' },
time: { fontSize: 12 , color: '#888' , marginTop: 4 },
});
Step 3: Odds Detail Screen
// screens/OddsScreen.tsx
import React , { useEffect , useState } from 'react' ;
import { ScrollView , Text , View , StyleSheet } from 'react-native' ;
import { apiGet } from '../api/fieldfunded' ;
interface Market {
id : string ;
name : string ;
selections : { id : string ; name : string ; odds : number }[];
}
export function OddsScreen ({ route } : any ) {
const { eventId } = route . params ;
const [ markets , setMarkets ] = useState < Market []>([]);
useEffect (() => {
apiGet <{ markets : Market [] }>( `/events/ ${ eventId } /odds` )
. then ( data => setMarkets ( data . markets || []));
}, [ eventId ]);
return (
< ScrollView style = {styles. container } >
{ markets . map ( market => (
< View key = {market. id } style = {styles. marketCard } >
< Text style = {styles. marketName } > {market. name } </ Text >
{ market . selections . map ( sel => (
< View key = {sel. id } style = {styles. selectionRow } >
< Text style = {styles. selName } > {sel. name } </ Text >
< Text style = {styles. odds } > {sel.odds.toFixed( 2 ) } </ Text >
</ View >
))}
</ View >
))}
</ ScrollView >
);
}
const styles = StyleSheet . create ({
container: { flex: 1 , padding: 16 },
marketCard: { marginBottom: 16 , padding: 12 , backgroundColor: '#f9f9f9' , borderRadius: 8 },
marketName: { fontSize: 14 , fontWeight: '700' , marginBottom: 8 },
selectionRow: { flexDirection: 'row' , justifyContent: 'space-between' , paddingVertical: 6 },
selName: { fontSize: 14 },
odds: { fontSize: 14 , fontWeight: '600' , color: '#2563eb' },
});
Step 4: Adaptive Polling for Live Scores
On mobile, polling frequency should adapt to save battery and data:
// hooks/useLiveScores.ts
import { useEffect , useRef , useState } from 'react' ;
import { AppState } from 'react-native' ;
import { apiGet } from '../api/fieldfunded' ;
export function useLiveScores ( sport : string ) {
const [ scores , setScores ] = useState < any []>([]);
const intervalRef = useRef < NodeJS . Timer | null >( null );
useEffect (() => {
const poll = async () => {
const data = await apiGet <{ scores : any [] }>( '/scores' , { sport });
setScores ( data . scores || []);
};
// Start polling
poll ();
intervalRef . current = setInterval ( poll , 30_000 ); // 30s when app is active
// Reduce polling when app is backgrounded
const sub = AppState . addEventListener ( 'change' , ( state ) => {
if ( intervalRef . current ) clearInterval ( intervalRef . current );
if ( state === 'active' ) {
poll (); // Immediate refresh on foreground
intervalRef . current = setInterval ( poll , 30_000 );
}
// Stop polling entirely when backgrounded
});
return () => {
if ( intervalRef . current ) clearInterval ( intervalRef . current );
sub . remove ();
};
}, [ sport ]);
return scores ;
}
Step 5: Local Caching
Cache API responses to reduce data usage and enable offline browsing:
// utils/cache.ts
import AsyncStorage from '@react-native-async-storage/async-storage' ;
const CACHE_TTL = 60_000 ; // 1 minute
interface CacheEntry < T > {
data : T ;
timestamp : number ;
}
export async function cachedFetch < T >(
key : string ,
fetcher : () => Promise < T >
) : Promise < T > {
const cached = await AsyncStorage . getItem ( key );
if ( cached ) {
const entry : CacheEntry < T > = JSON . parse ( cached );
if ( Date . now () - entry . timestamp < CACHE_TTL ) {
return entry . data ;
}
}
const data = await fetcher ();
await AsyncStorage . setItem ( key , JSON . stringify ({
data ,
timestamp: Date . now (),
}));
return data ;
}
Rate Limit Math for Mobile
Mobile apps typically make fewer requests than web dashboards because of adaptive polling:
Scenario Requests/day Monthly Plan Browse events + check odds (casual) 20 600 Free Track 3 live matches with 30s polling 600 18,000 Starter ($29) Production app with 100 daily users 3,000 90,000 Starter ($29) High-traffic app with 1,000+ users 15,000 450,000 Pro ($79)
With caching, most user actions hit the local cache instead of the API. A well-optimized mobile app can serve 100 daily active users on the Starter plan .
Get Your Free API Key Start building in 5 minutes — works with any mobile framework
See Pricing All plans compared side by side