Background execution
Background tracking is the most divergent area between platforms. The state machine, presets, gate, and schema are uniform — but the OS mechanics that keep your app alive differ fundamentally. Beekon picks the right mechanism per platform; you just need to understand what it’s doing so you can debug when it doesn’t.
Android
Section titled “Android”Beekon runs a foreground service typed FOREGROUND_SERVICE_LOCATION (Android 14+) backed by FusedLocationProviderClient. The service holds a partial wake lock for the duration of tracking.
| Mechanism | Role |
|---|---|
FusedLocationProviderClient | the actual location source — fuses GPS, Wi-Fi, cell, sensor signals |
| Foreground service + notification | satisfies Android 8+ background-location rules; visible to user |
FOREGROUND_SERVICE_LOCATION type | required for Android 14+ to start a location FGS |
| Boot receiver | auto-restarts tracking after device reboot if it was previously enabled |
WorkManager | restart-if-killed, deferred work hook |
The foreground notification is required by the platform, not a Beekon design choice. Pick a reasonable channel name and body; Beekon creates the notification channel on Android 8+ on your behalf.
What can still kill the service
Section titled “What can still kill the service”Even with everything wired correctly, aggressive OEM battery managers can kill foreground services on devices from Xiaomi, Huawei, Samsung, OPPO, Vivo, and others. The dontkillmyapp.com matrix maps OEM-specific workarounds (auto-start whitelisting, battery-optimisation exemption, etc.). Surface those user-facing instructions in your app’s onboarding for affected devices.
Beekon’s state holder survives the service’s death and respawn — your state and positions Flow identities don’t change across service restarts. If the OS kills the service entirely, WorkManager schedules a restart and the boot receiver covers reboot.
Doze and App Standby
Section titled “Doze and App Standby”Android’s Doze mode (device idle) and App Standby (per-app idle) reduce wake-up frequency for backgrounded apps. The foreground service exempts you from most of this, but not all of it — expect coarser update cadence during deep doze. Beekon’s gate means you simply emit fewer positions during these periods rather than dropping mid-trip.
iOS background execution is fundamentally about whether your app is alive at all. Beekon picks the modern API on iOS 17+ and falls back to the legacy delegate on iOS 15–16 — your app sees one shape via Beekon.shared.
iOS 17+
Section titled “iOS 17+”CLLocationUpdate.liveUpdates(LiveUpdateConfiguration)+ CLBackgroundActivitySession // keeps background delivery alive+ CLServiceSession(authorization: .always) // iOS 18+ only, declared-authorizationCLLocationUpdate.liveUpdates is the async sequence Apple introduced to replace the delegate. CLBackgroundActivitySession is what makes it survive backgrounding. Beekon holds these in StateHolder.
The iOS 17+ path’s preset configuration maps to the framework’s preset:
| Beekon preset | CLLocationUpdate configuration |
|---|---|
| Saver | .default |
| Balanced | .default |
| Precision | .automotiveNavigation |
iOS 15–16
Section titled “iOS 15–16”CLLocationManager .startUpdatingLocation() .allowsBackgroundLocationUpdates = true .showsBackgroundLocationIndicator = trueWrapped in an AsyncStream so StateHolder consumes one shape regardless of OS.
Significant Location Change (every version)
Section titled “Significant Location Change (every version)”Beekon runs a Significant Location Change monitor alongside liveUpdates. SLC is the only mechanism that wakes a terminated iOS app from coarse movement; liveUpdates does not.
When SLC wakes your app into a fresh process:
Beekonreads the persisted tracking intent from aUserDefaultssuite (com.wayq.beekon).- If the app was tracking before termination, Beekon auto-resumes tracking on relaunch.
- The fresh process has a new
statestream — re-subscribe; the latest state replays.
This is why you don’t need a restoreTracking() call — Beekon does it.
Authorization
Section titled “Authorization”You need Always authorization for background tracking. iOS forces a two-step prompt: WhenInUse first, then Always after the user has used the relevant feature once. Asking for Always straight away is silently denied.
Beekon does not drive these prompts — the host app does, because timing and copy depend on your UX. See iOS setup.
Flutter
Section titled “Flutter”The plugin sits on top of the native libraries — Beekon’s background execution on Android and iOS works the same whether you’re on a native or Flutter app. The Flutter engine itself can be torn down in background; that’s why the persistence write path lives in the native library, not in Dart.
When the Flutter engine is alive:
stateandpositionsstreams flow normally.history(from, to)queries the native DB.
When the Flutter engine is dead (background, terminated):
- Native library keeps tracking; positions accumulate in the DB.
- Streams emit nothing to Dart — there’s no Dart to emit to.
- Re-launching the app gets you back the live state and the full history.
This means you should never rely on a Dart-side stream listener to react to a background position. Use the native DB and read it on resume.
Sample-app testing
Section titled “Sample-app testing”For end-to-end background validation, the sample apps in the repo are the canonical test rigs:
beekon-android/sample— Compose app with start/stop, live map, history viewbeekon-ios/Sample— SwiftUI app, equivalent shapebeekon_flutter/example— Flutter app, exercises both platforms
A real-device 30–60 minute walk with the app backgrounded is the only way to truly verify background reliability — emulators and simulators lie about doze/SLC.