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

> Build a mobile sports odds app with React Native and FieldFunded API. Live odds, scores, and settlement on iOS and Android.

# 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/events`           | List upcoming matches    |
| `getEventOdds()` | `GET /v1/events/{id}/odds` | Get odds for a match     |
| `getLive()`      | `GET /v1/live`             | Currently in-play events |
| `getScores()`    | `GET /v1/scores`           | Live scores              |
| `checkBet()`     | `POST /v1/bets/check`      | Settle bets              |

## Step 1: Set Up the API Client

Create a lightweight client optimized for mobile:

```typescript theme={null}
// 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

```typescript theme={null}
// 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

```typescript theme={null}
// 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:

```typescript theme={null}
// 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:

```typescript theme={null}
// 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](/compare/pricing).

***

## Related Guides

* [Build a Betting Platform](/guides/build-betting-platform) — full-stack web architecture
* [Build a Soccer Odds App](/guides/build-soccer-odds-app) — soccer-specific implementation
* [Build a Real-Time Dashboard](/guides/build-realtime-dashboard) — WebSocket-based live updates
* [Free Sports Odds API](/compare/free-sports-api) — start with the free tier

<CardGroup cols={2}>
  <Card title="Get Your Free API Key" icon="key" href="/quickstart">
    Start building in 5 minutes — works with any mobile framework
  </Card>

  <Card title="See Pricing" icon="dollar-sign" href="/compare/pricing">
    All plans compared side by side
  </Card>
</CardGroup>
