Background Location on Expo That Actually Works: North's Pipeline
- React Native
- Expo
- geolocation
- iOS
- Android
- Supabase
- North
North is a family location app built on Expo, and its hardest engineering problem isn't the map — it's existing at all. Mobile OSes are explicitly designed to stop apps from doing what a location-sharing app must do: run in the background. Here's the pipeline that keeps a circle's map honest without destroying anyone's battery.
Two location profiles, not one
The first mistake everyone makes is one set of location options. North runs two:
- Foreground: balanced accuracy, updates every 20s or 25m of movement — responsive while you're actually looking at the map.
- Background: coarse by design — 150m distance intervals with deferred updates, so the OS batches fixes and delivers them in bursts instead of waking the app per-fix.
pausesUpdatesAutomaticallystays on, and on Android there's deliberately no foreground service — no permanent notification squatting in the tray. The cost is accepting Android's background throttle (a few updates per hour); the alternative is the surveillance-app aesthetic North exists to reject.
The stationary problem and layered heartbeats
Distance-based updates have a blind spot: a phone sitting still emits nothing, and "no update" is indistinguishable from "app dead." North layers three heartbeats over the gap:
- Foreground heartbeat — a 30-second timer forcing a fix while the app is open, because iOS suspends stationary updates even in the foreground.
- Background heartbeat task — periodic wakes via
expo-background-taskthat, crucially, reuse the cached last-known position when the device hasn't moved, re-uploading it with a fresh timestamp. The signal recipients actually need — "this data is current" — costs zero GPS power. - On-demand wakes — the clever one. When someone opens the map, the client calls a Supabase Edge Function that sends silent pushes (
content-available, no alert) to the other circle members' devices. Each sleeping phone wakes a background task, checks its sharing and battery-frequency guards, takes one fix, uploads, and goes back to sleep. Fresh data appears precisely when someone is looking — demand-driven instead of polling.
The push priorities differ per platform on purpose: normal (5) on APNs where silent pushes are throttled aggressively anyway, high on Android to punch through Doze.
The pipeline downstream
Every fix flows through the same path before upload: activity classification (covered in its own post), then an optional privacy blur (also its own post), then a single pushLocation writer to Supabase. Viewers subscribe over Supabase Realtime.
Realtime has its own quiet failure mode: drop the connection, reconnect, and the updates from the gap are simply gone. North handles it with a backfill on resubscribe — every reconnect refetches current member state rather than trusting the stream — plus reconnect backoff capped at 30 seconds. Boring plumbing, but it's the difference between "my daughter's dot is wrong" and an app you trust.
What I'd tell anyone building this
- Design for death. Your background process will be killed. The question is what the rest of the system does about it — which is a big enough topic that the reconnect-nudge system has its own write-up.
- Cached fixes are underrated. Most "is this fresh?" questions don't need new GPS data; they need a new timestamp on honest old data.
- Make staleness visible instead of hiding it. When updates do stop, North's roster says "last seen 25 min ago" rather than pretending — the honest staleness decision.
- Silent pushes are your remote control. A push token is a way to run a function on someone's phone. Used sparingly and guarded well, it converts "always tracking" into "available on request" — better for battery and for the product's soul.