After building a walk tracking feature, I went for a real test: 20 minutes around the neighborhood. The results were bad -- 18 GPS points, a 15-minute gap in data, and an incomplete path on the map. The walk started fine, then the phone went in my pocket, the screen locked, and... silence.

The Data

SELECT timestamp FROM gps_points WHERE walk_id = 16 ORDER BY timestamp;
-- 12:48:17
-- 12:48:19
-- 12:48:23
-- 12:48:26  <-- last point before gap
-- ... nothing for 15 minutes ...
-- 13:03:55  <-- first point after gap

GPS tracking stopped when the screen locked.

The Problem

The original code used watchPositionAsync -- a foreground-only API:

// Only works while app is in foreground!
const subscription = await Location.watchPositionAsync(
  { accuracy: Location.Accuracy.High, timeInterval: 5000 },
  (location) => {
    recordGpsPoint(location);  // Stops firing when iOS suspends the app
  }
);

iOS aggressively suspends apps to save battery. When the screen locks, the location subscription pauses.

The Fix

Switch to Expo's background location system using TaskManager:

import * as TaskManager from 'expo-task-manager';

// CRITICAL: must be called at module level, not inside a function
TaskManager.defineTask('WALK_LOCATION_TASK', async ({ data, error }) => {
  if (error || !data) return;
  const { locations } = data;
  for (const location of locations) {
    await saveGpsPoint({
      latitude: location.coords.latitude,
      longitude: location.coords.longitude,
      timestamp: new Date(location.timestamp).toISOString(),
    });
  }
});

Then start background updates instead of foreground watching:

await Location.startLocationUpdatesAsync('WALK_LOCATION_TASK', {
  accuracy: Location.Accuracy.BestForNavigation,
  timeInterval: 3000,
  distanceInterval: 3,
  activityType: Location.ActivityType.Fitness,
  showsBackgroundLocationIndicator: true,
  pausesUpdatesAutomatically: false,
});

The key iOS settings: activityType: Fitness optimizes for walking patterns, showsBackgroundLocationIndicator shows the blue bar, and pausesUpdatesAutomatically: false prevents iOS from pausing updates.

Results

Metric Before After
GPS points (20 min) 18 400+
Coverage gaps 15+ minutes None
Works with screen off No Yes

The foreground-only approach works great during development when you're staring at the screen. It's only when you go for a real walk with your phone in your pocket that you discover the problem.