Background Location on Android Is a Battery-Manager Fight
Every Android location tracker is unreliable for the same reason — OEM battery managers kill background processes you don't own. The fix was ~150 lines of Kotlin: a daily WorkManager job that POSTs a city-only fix to my own endpoint.
The /now page on this site has a Location row. I wanted it to show the city I’m actually in, updating on its own as I travel. Simple ask. The phone side is where it dies.
First I tried existing trackers. HTTP Request Shortcuts wanted too much wiring to get GPS into a request body. OwnTracks is the standard recommendation, and its reviews are full of the same complaint: it silently stops reporting after a while. A web app — or anything Google AI Studio generates — is a non-starter, because browsers suspend navigator.geolocation the moment the tab is backgrounded.
None of these are really bugs. They’re the same fact wearing different clothes: Android’s OEM battery managers kill background work aggressively, and you cannot durably whitelist behavior you don’t own. You can whitelist your own app. So — ~150 lines of native Kotlin.
The architecture turned out smaller than the debugging. A WorkManager PeriodicWorkRequest every 24h — deliberately not a foreground service, which would mean a permanent notification running all day to fire one request. A FusedLocationProviderClient.getCurrentLocation() one-shot inside the worker, then an HttpURLConnection POST to my own endpoint. A “Send now” button enqueues a OneTimeWorkRequest for when I land somewhere and want it immediately.
The one non-obvious trap: ACCESS_BACKGROUND_LOCATION is a separate, second permission prompt on Android 11+, and a WorkManager job runs in the background — so “while using the app” silently fails the worker’s permission check with no error. It has to be “Allow all the time.”
The endpoint reverse-geocodes the fix (BigDataCloud, no key) and stores city + country only. The raw lat/lng is dropped immediately and never rendered. Precise coordinates never leave the request.
One bug earned its keep. An early {"lat":0,"lng":0} test ping reverse-geocoded to the middle of the Atlantic and sat on my homepage as Atlantic Ocean · last ping 3w ago until I noticed. Null island. The endpoint now rejects anything within half a degree of 0,0.
Lesson: “background location is unreliable on Android” is really “background anything is unreliable unless the OS trusts the app.” The fix isn’t a better library — it’s owning the process you’re allowed to exempt.
// Discussion
Comments are powered by GitHub Discussions via Giscus. Sign in with your GitHub account to add a reply, or discuss on X.