Lifecycle & states
Beekon goes through exactly three states. They’re identical on every platform — only the casing follows the host language’s idiom (Tracking on Kotlin, tracking on Swift).
Idle → Tracking → Stopped(reason)You observe state via the state stream — it’s hot and replay-1, so every consumer sees the latest value the moment they subscribe.
The states
Section titled “The states”| State | Meaning | Live emissions? |
|---|---|---|
Idle | Initial state. configure may or may not have happened; start has not. | No |
Tracking | Live updates flowing from the OS provider through the gate. | Yes |
Stopped(reason) | Tracking ended. The reason indicates whether it was user-initiated or forced. Re-enter via start(). | No |
There’s no Starting window — start() is suspending and is the transition into Tracking. There’s no Paused state either — preconditions that fail at runtime (permission revoked, location services disabled, OS-killed foreground service on Android) surface as a terminal Stopped(reason) and the host app calls start() again once the condition recovers.
Stop reasons
Section titled “Stop reasons”| Reason | When it fires |
|---|---|
User | The host app called Beekon.stop(). |
PermissionDenied | Location permission was revoked or never granted. |
LocationServicesDisabled | System-wide location services were turned off (iOS), or services off / Google Play Services unavailable (Android). |
LocationUnavailable | The provider can’t produce fixes (no signal / hardware unavailable). |
System | The OS terminated tracking — on Android, a foreground-service kill (memory pressure, force-stop). |
Stop reasons match across platforms (PermissionDenied on Kotlin, .permissionDenied on Swift, 'permissionDenied' on the wrappers).
Observing state
Section titled “Observing state”// state is a StateFlow — replay-1, hotval state by Beekon.state.collectAsStateWithLifecycle()
when (val s = state) { is BeekonState.Idle -> Text("Not started") is BeekonState.Tracking -> Text("Live") is BeekonState.Stopped -> Text("Stopped: ${s.reason}")}Task { for await state in await Beekon.shared.state { switch state { case .idle: Text("Not started") case .tracking: Text("Live") case .stopped(let reason): Text("Stopped: \(reason)") } }}Beekon.instance.state.listen((state) { switch (state) { case Idle(): print('Not started'); case Tracking(): print('Live'); case Stopped(:final reason): print('Stopped: $reason'); }});Beekon.onState((state) => { if (state.kind === 'stopped') { console.log('Stopped:', state.reason); } else { console.log(state.kind); // 'idle' | 'tracking' }});Transitions
Section titled “Transitions” start() stop() / OS-revocation / system killIdle ────────> Tracking ──────────────────────────────> Stopped(reason) ▲ │ └────────────── start() ─────────────────────┘Idle → Tracking:start()succeeds. The first admitted fix can arrive immediately.Tracking → Stopped(User): the host calledstop().Tracking → Stopped(PermissionDenied): authorization was revoked while tracking.Tracking → Stopped(LocationServicesDisabled): system services off, or — on Android — Google Play Services unavailable.Tracking → Stopped(System): Android only — the foreground service was killed by the OS.Stopped → Tracking: requires a freshstart(). Beekon does not auto-resume when a condition recovers.