Skip to content

Persistence & history

Every gated position is persisted to a SQLite database that the native library owns. The schema, retention rules, and write semantics are identical across platforms — only the engine differs (Room on Android, GRDB on iOS).

This is the rule that shapes everything else: the persistence write path is invoked from the platform’s native location callback and never crosses into Dart or JavaScript. In background, those runtimes are not guaranteed to be running — a missed write would be a lost point. So:

  • Android: write happens inside the foreground service, on a background dispatcher, fed from the FusedLocationProviderClient callback.
  • iOS: write happens synchronously from the CLLocationUpdate.liveUpdates (or legacy delegate) callback inside StateHolder.deliverLocation.
  • Flutter / RN bridges expose history(from, to) for reads only. They never participate in writes.

Each row corresponds to one emitted (gated) position.

ColumnTypeMeaning
tsINTEGERUnix milliseconds (timestamp from the OS provider, not the time we received it)
latREALDegrees
lngREALDegrees
accuracyREALMetres, horizontal 1σ
speedREALMetres / second
bearingREALDegrees, 0–360 (true north)
altitudeREALMetres above WGS-84 ellipsoid
activity_hintTEXT (nullable)Reserved for future activity recognition (always NULL in v1)

There’s an index on ts ASC for range queries. Both platforms keep the schema in lockstep — migrations bump version on both adapters together.

PlatformPath
AndroidApp-private data (Room default), inside the foreground-service process
iOSLibrary/Application Support/beekon/beekon.db, marked isExcludedFromBackup = true

iOS deliberately excludes the DB from iCloud — silent sync of a 100K-row trip database isn’t user-friendly behaviour.

A two-axis policy that runs on every write batch:

TTL: 7 days
Cap: 100,000 rows

After each write, the adapter prunes rows older than 7 days and trims to 100K rows from the oldest end. Both bounds are normative — same on every platform.

If you’re deleting historical user data on demand (GDPR-style “delete my account”), wipe the DB at the OS level (uninstall the app, or clear app data on Android). Beekon doesn’t expose a clearHistory() API in v1.

history(from, to) returns positions inclusive of both bounds.

val now = Instant.now()
val hourAgo = now.minus(1, ChronoUnit.HOURS)
// suspend, throws BeekonError.InternalError on DB failure
val points: List<Position> = Beekon.history(from = hourAgo, to = now)

Returned list is sorted ascending by timestamp.

Reads and writes share the same database. Both engines (Room, GRDB) use WAL mode, so concurrent readers don’t block the writer and vice versa. You can call history while Beekon.state == Tracking without affecting emission cadence.

  • It doesn’t sync to a cloud — that’s your application’s job. Beekon’s console test-rig demonstrates an ingest endpoint, but it’s not part of the SDK contract.
  • It doesn’t expose the underlying SQLite handle. If you need to run custom queries, query Beekon’s history range and post-process in your code.
  • It doesn’t store anything other than positions. State transitions, errors, and pause reasons are not persisted.