Errors
Beekon errors are typed (Kotlin: subclasses of BeekonError : Throwable; Swift: cases of enum BeekonError: Error; Dart: subclasses of BeekonException). No global error state, no error codes — pattern-match the type and act on it.
The set is small by design. Most errors map to a precondition violation (NotInitialised, NotConfigured) or a permission/availability failure (PermissionDenied, LocationServicesDisabled, NoGmsAvailable). Internal errors should be rare.
The taxonomy
Section titled “The taxonomy”| Error | Platforms | When it fires | What to do |
|---|---|---|---|
NotInitialised | All | start() called before initialize() | Call initialize() once at app startup |
NotConfigured | All | start() called before configure(), or required field missing | Call configure(config) with a complete BeekonConfig |
PermissionDenied | All | Runtime location permission missing | Drive the OS permission prompt, retry start() |
LocationServicesDisabled | iOS, Flutter | System-wide location services off (Settings → Privacy → Location Services) | Show a “turn on Location Services” hint; on Android, surface as Paused(LocationDisabled) instead |
NoGmsAvailable | Android, Flutter | Device has no Google Play Services | v1 doesn’t support non-GMS devices — surface gracefully |
ServiceFailed(cause) | Android, Flutter | Foreground service start failure (quota, OEM kill on start) | Log cause, retry; if persistent, OEM-specific workaround likely needed |
InternalError(cause) | All | Anything else (DB failure, unexpected platform error) | Log and surface; should be rare |
LocationServicesDisabled is iOS-only on the typed-throw side; on Android the same condition surfaces as Paused(LocationDisabled) via the state stream rather than a thrown error. This is a deliberate platform-specific behaviour — the spec calls it out as not-yet-mirrored.
Handling
Section titled “Handling”suspend fun startTracking() { try { Beekon.start() } catch (e: BeekonError.NotInitialised) { // bug — should never happen if you initialize in Application.onCreate Log.e("beekon", "init missing", e) } catch (e: BeekonError.NotConfigured) { // first run before configure — call configure(...) and retry } catch (e: BeekonError.PermissionDenied) { requestLocationPermissions { granted -> if (granted) startTracking() } } catch (e: BeekonError.NoGmsAvailable) { showAlert("Beekon requires Google Play Services on this device.") } catch (e: BeekonError.ServiceFailed) { Log.e("beekon", "FGS start failed", e.cause) // typically retry-able; if it persists, the device may need OEM-specific exemption } catch (e: BeekonError.InternalError) { Log.e("beekon", "internal", e.cause) }}do { try await Beekon.shared.start()} catch BeekonError.notInitialised { // bug — call initialize() at app startup} catch BeekonError.notConfigured { // call configure(...) and retry} catch BeekonError.permissionDenied { // drive the WhenInUse → Always prompt; iOS may have silently denied Always permissionManager.requestAlways()} catch BeekonError.locationServicesDisabled { showAlert("Location Services are off in Settings → Privacy & Security.")} catch BeekonError.internalError(let cause) { print("beekon internal: \(cause)")}try { await Beekon.instance.start();} on NotInitialised { // call initialize() at app startup} on NotConfigured { // call configure(...) and retry} on PermissionDenied { // request runtime perms (Android) or drive Always prompt (iOS)} on LocationServicesDisabled { // iOS: services off in Settings} on NoGmsAvailable { // Android: no Google Play Services} on ServiceFailed catch (e) { // Android FGS start failed; e.cause has the underlying message} on InternalError catch (e) { // log; should be rare}Errors vs paused states
Section titled “Errors vs paused states”A useful distinction: errors are thrown when an operation cannot proceed; paused states are reported when tracking was running and an external precondition changed.
| Scenario | Surface |
|---|---|
User has never granted permission, you call start() | throws PermissionDenied |
| User revokes permission while tracking | state transitions to Paused(PermissionRevoked) — start() is not called and does not throw |
Both code paths exist. Most apps observe the state stream for the paused case and try/catch around start() calls.
What’s not surfaced
Section titled “What’s not surfaced”- Per-position errors (e.g. one bad fix from the OS provider). Beekon doesn’t filter these — see Positions.
- Notification-channel errors on Android. Beekon creates the channel and continues even if it can’t be displayed.
- DB write retries.
InternalError(cause)only fires after retries are exhausted, which should be rare.