Lifecycle & states
Beekon goes through exactly five states. They’re identical on every platform — only the casing follows the host language’s idiom (Tracking on Kotlin, tracking on Swift).
Idle → Starting → Tracking ⇄ Paused(reason) → StoppedYou 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 | Position emissions? |
|---|---|---|
Idle | Initial state. initialize/configure happened, start has not. | No |
Starting | start() was called; the platform is bringing up its location pipeline. | No |
Tracking | Live updates flowing from the OS provider through the gate. | Yes |
Paused(reason) | Something external interrupted tracking — see pause reasons. | No |
Stopped | stop() was called; the platform pipeline is torn down. Re-enter via start(). | No |
A shutdown() call is rarely needed — it tears down the state holder entirely. Most apps just leave Beekon at Stopped between sessions.
Pause reasons
Section titled “Pause reasons”Pauses happen when a precondition that was true at start() becomes false at runtime. Beekon never throws from a paused state — it transitions and the corresponding error class is what start() would have thrown if you’d called it again from scratch.
| Reason | When it fires |
|---|---|
PermissionRevoked | User revoked location permission from Settings while tracking. |
LocationDisabled | System-wide Location Services were turned off (iOS), or a similar disable on Android. |
Unknown | Catch-all for transient OS errors. Usually transient — Beekon tries to recover. |
Pause reasons match across platforms (permissionRevoked on Swift/Dart, PermissionRevoked on Kotlin).
Observing state
Section titled “Observing state”// state is a StateFlow — replay-1, hotval state by Beekon.state.collectAsStateWithLifecycle()
when (state) { is BeekonState.Idle -> Text("Not started") is BeekonState.Starting -> CircularProgressIndicator() is BeekonState.Tracking -> Text("Live") is BeekonState.Paused -> Text("Paused: ${(state as BeekonState.Paused).reason}") is BeekonState.Stopped -> Text("Stopped")}Task { for await state in await Beekon.shared.state { switch state { case .idle: Text("Not started") case .starting: ProgressView() case .tracking: Text("Live") case .paused(let reason): Text("Paused: \(reason)") case .stopped: Text("Stopped") } }}StreamBuilder<BeekonState>( stream: Beekon.instance.state, initialData: const Idle(), builder: (ctx, snap) => switch (snap.data!) { Idle() => const Text('Not started'), Starting() => const CircularProgressIndicator(), Tracking() => const Text('Live'), Paused(:final reason) => Text('Paused: $reason'), Stopped() => const Text('Stopped'), },);Transitions
Section titled “Transitions” start() ┌────────────────────────┐ │ ▼ Idle ──> Starting ──> Tracking ──> Stopped ▲ │ ▲ │ ▼ │ └─── Paused(reason) ────────┘ ▲ │ permission revoked, location services off, transient OS errorStarting → Trackinghappens after the first OS fix lands (or immediately on iOS 17+).Starting → Pausedhappens if the precondition fails immediately (e.g. permission was revoked betweenconfigureandstart).Paused → Trackinghappens automatically when the underlying condition recovers (permission re-granted, services re-enabled).Stopped → Trackingrequires a freshstart()call.