A React Native running app must upload the GPS track while backgrounded; why is a periodic background task not enough, and how would you design the sync?
answer
- deferrable work is the wrong tool
- location mode keeps the app alive
- sync inside the location task
- persist points, then upload batches
- background task only as a backstop
basics
~20 sA periodic expo-background-task runs when the OS chooses, possibly hours later. Background location keeps the app running, so sync inside the TaskManager location task: persist each batch, then upload pending points, with a periodic task as backstop.
solid answer
~40 s`expo-background-task` is deferrable: the OS runs its one worker when conditions suit it, `minimumInterval` is only a floor, and iOS often defers such work, so it cannot deliver a live track. What keeps the app running during a run is background location: `Location.startLocationUpdatesAsync(taskName, options)` with the iOS location background mode and an Android foreground service calls a `TaskManager` task with `data.locations` while the app is in the background. So the sync goes there: define the task at module top level, append each batch to durable storage with sequence numbers, then upload pending points when enough have accumulated, leaving them queued on failure. Tune delivery with `deferredUpdatesInterval` and `deferredUpdatesDistance`, flush on stop and on return to the foreground, and keep a periodic task only as a backstop.
code
typescript · 31 lines// tasks/track.ts, imported from the app entry
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
import { appendPoints, uploadPendingPoints } from '../trackStore';
export const TRACK_TASK = 'run-track';
TaskManager.defineTask<{ locations: Location.LocationObject[] }>(
TRACK_TASK,
async ({ data, error }) => {
if (error || !data) return;
await appendPoints(data.locations); // durable before any network call
try {
await uploadPendingPoints({ minBatch: 20 });
} catch {
// Points stay pending; the next batch or the next foreground retries.
}
},
);
export function startRunTracking() {
return Location.startLocationUpdatesAsync(TRACK_TASK, {
accuracy: Location.Accuracy.BestForNavigation,
activityType: Location.ActivityType.Fitness,
deferredUpdatesInterval: 60_000,
foregroundService: {
notificationTitle: 'Run in progress',
notificationBody: 'Recording your route',
},
});
}go deeper
Know that periodic background tasks run whenever the OS decides, and that continuous tracking needs background location, which calls a TaskManager task while the app is in the background.
Explain why the location task is the reliable place to run code during a run, and why the task must be defined at module top level.
Lay out the full design: persist then upload in idempotent batches, tune deferred updates, flush on stop and on foreground, handle a denied background permission, and test with the screen off.
Weigh live tracking against battery, permission friction and store review, and decide whether near-live sync is worth it or post-run upload is the better product.
## The problem A running app records the user's route. During a 45-minute run the phone is in a pocket with the screen off, and the product wants the track on the server as the run goes, so a friend can follow it live and nothing is lost if the phone dies. The tempting design is a periodic `expo-background-task` that uploads whatever has been recorded. It does not work, for reasons that are all about how background execution is granted. ## Why a periodic task is the wrong tool - `expo-background-task` is **deferrable** work. The OS runs its single worker when battery, network and usage patterns suit it; `minimumInterval` is only a floor, and iOS often defers such work to windows like overnight. A run may happen long after the user finishes, or not at all today. - A background task is **not** what keeps the app alive during the run. Continuous work needs a background mode the OS grants for a reason: here, location. - Timers do not help either. Once the app is backgrounded, React Native's timers are paused or the process is suspended, so a `setInterval` upload loop simply stops. ## What does keep the app running: background location `expo-location`'s `startLocationUpdatesAsync(taskName, options)` subscribes to location updates delivered to a `TaskManager` task. With background location enabled (the location background mode on iOS, a foreground service with a visible notification on Android, both set up through `expo-location`'s config plugin), the OS keeps delivering locations while the app is in the background, and `expo-task-manager` calls your task with `data.locations`, whether or not any screen is mounted. That task invocation is the **only reliable moment** you get to run JavaScript during the run, so the sync belongs there. ## The design 1. **Define the location task at module top level.** The task can run with no screen mounted, so the definition must be in a module the entry imports. 2. **Persist first, upload second.** Each invocation appends its points to durable storage with a monotonically increasing sequence number before any network call. If the process dies after that line, nothing is lost. 3. **Upload opportunistically in batches.** After persisting, upload the pending points if enough have accumulated or enough time has passed since the last successful upload. On failure, leave them pending; the next invocation tries again. 4. **Make uploads safe to repeat.** Send the sequence range with the batch so the server can ignore points it already has; a retry after a timeout must not duplicate the track. 5. **Control the invocation rate.** `deferredUpdatesInterval` (milliseconds) and `deferredUpdatesDistance` (metres) batch location delivery while backgrounded, which trades update frequency for battery. 6. **Close out the run.** When the user stops, call `Location.stopLocationUpdatesAsync(taskName)` and flush. Anything still pending is retried on the next foreground, with a periodic `expo-background-task` as a **backstop** only. | Mechanism | Role in this design | |---|---| | Location task via `TaskManager.defineTask` | Primary: persist and upload during the run | | AppState returning to `active` | Flush on every return to the app | | `expo-background-task` | Backstop for leftovers after the run, best effort | | `setInterval` upload loop | None: stops when backgrounded | ## Walking through a failure Suppose Android kills the process twenty minutes into the run because of memory pressure. With the naive design, a `setInterval` loop and an in-memory array, the last few minutes of points are gone and the upload loop is dead until the user opens the app. With the design above, every batch the location task received before the kill is already on disk with its sequence numbers. The next time the location task runs, or the user reopens the app, the pending points are uploaded starting from the last sequence number the server acknowledged. The server sees a gap in time, not in data, and a retried batch is ignored because its sequence range is already stored. ## What to say about limits - Background location requires the user to grant background (or "Always") permission, and the Expo docs note that Google Play reviews apps that request it; a declined permission must degrade to foreground-only tracking. - The Android foreground service notification is part of the contract, not decoration. - Keep the task's work small. The executor runs for every batch, and a slow upload delays the next persist. - Test on real devices with the screen off and after killing the app, since development builds with the UI open hide every failure mode above.
- In this React Native tracking design, why must the location task persist points before trying to upload them?The process can be suspended or killed at any moment in the background, and the network call is the slowest, least reliable step. Writing the batch to durable storage first means a crash or a failed upload loses nothing: the points stay pending with their sequence numbers, and the next invocation or the next foreground session uploads them.
- What happens to the location-driven sync if the user grants only 'while using the app' location permission?Background location updates are not delivered, so the task stops being invoked once the app leaves the foreground. The app must detect the missing background permission, tell the user live tracking is limited, record while in the foreground, and upload the rest when the app returns to the foreground or through the backstop task.
saying these in an interview costs you the question
- A 15-minute expo-background-task gives the server a near-live track.
- A setInterval upload loop keeps running while the phone is in a pocket.
- Uploading directly from the location task without persisting first is safe enough.
- The location task can read the current run from React state.
- Background location needs no extra permission beyond foreground location.