Fixing iOS Background Location Tracking in Expo
A 20-minute walk test captured only 18 GPS points with a 15-minute data gap. The screen locked, and iOS killed the location subscription.
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.
Built while working on PlanPal.
Back to danielships ->