Geofencing
Beekon can watch circular regions and tell you when the device crosses their boundary. Geofences are independent of tracking — they keep firing across start() / stop() cycles, survive process death, and survive reboot. You don’t have to be actively tracking for a crossing to arrive.
Under the hood it’s the platform’s own geofencing engine — GMS GeofencingClient on Android (auto-paged, so you can register more than the OS per-app limit) and CLMonitor on iOS. The crossing vocabulary is Enter / Exit only; Android’s DWELL transition is not surfaced.
These are your geofences — self-managed mode. In Beekon Cloud the server owns the set, and addGeofences reports GeofencesManagedByServer instead.
BeekonGeofence
Section titled “BeekonGeofence”A geofence is a circle plus two notify flags:
| Field | Type | Range | Default |
|---|---|---|---|
id | string | 1 … 100 chars; re-adding a matching id replaces it | — |
latitude | number (°) | −90 … +90, WGS-84 | — |
longitude | number (°) | −180 … +180, WGS-84 | — |
radiusMeters | number (m) | > 0; ≥ 100 recommended for reliable triggering | — |
notifyOnEntry | bool | — | true |
notifyOnExit | bool | — | true |
The OS geofencing backends are reliable from roughly 100 m up — a tighter radius triggers late or not at all, so prefer ≥ 100 even though anything > 0 is accepted.
The three methods
Section titled “The three methods”Geofence management is add / remove / list — there’s no setGeofences.
| Method | Behaviour |
|---|---|
addGeofences(list) | Register geofences. Re-adding an existing id replaces it. Atomic — if any entry fails validation, none are added. |
removeGeofences(ids) | Unregister by id. Unknown ids are ignored. |
listGeofences() | The currently registered set. |
Beekon.addGeofences(listOf( BeekonGeofence(id = "home", latitude = 12.9716, longitude = 77.5946, radiusMeters = 150.0),))val current: List<BeekonGeofence> = Beekon.listGeofences()Beekon.removeGeofences(listOf("home"))// addGeofences throws BeekonException.InvalidGeofence on a bad entrytry await Beekon.shared.addGeofences([ BeekonGeofence(id: "home", latitude: 12.9716, longitude: 77.5946, radiusMeters: 150),])let current = await Beekon.shared.listGeofences()await Beekon.shared.removeGeofences(ids: ["home"])// addGeofences throws BeekonError.invalidGeofence on a bad entryawait Beekon.instance.addGeofences([ BeekonGeofence(id: 'home', latitude: 12.9716, longitude: 77.5946, radiusMeters: 150),]);final current = await Beekon.instance.listGeofences();await Beekon.instance.removeGeofences(['home']);await Beekon.addGeofences([ { id: 'home', latitude: 12.9716, longitude: 77.5946, radiusMeters: 150 },]);const current = await Beekon.listGeofences();await Beekon.removeGeofences(['home']);The geofenceEvents stream
Section titled “The geofenceEvents stream”Crossings arrive on the geofenceEvents stream as a GeofenceEvent:
| Field | Meaning |
|---|---|
id | Unique event id (SDK-generated) — the sync dedup key. |
geofenceId | The BeekonGeofence.id that was crossed. |
type | Enter or Exit. |
timestamp | When the crossing happened (UTC). |
Beekon.geofenceEvents.collect { e -> Log.d("beekon", "${e.type} ${e.geofenceId} @ ${e.timestamp}")}for await e in await Beekon.shared.geofenceEvents { print("\(e.type) \(e.geofenceId) @ \(e.timestamp)")}Beekon.instance.geofenceEvents.listen((e) { print('${e.type} ${e.geofenceId} @ ${e.timestamp}');});Beekon.onGeofenceEvent((e) => { console.log(e.type, e.geofenceId, e.timestamp);});Persistence & lifecycle
Section titled “Persistence & lifecycle”Registered geofences are persisted by the native SDK, so they outlive the process. A crossing can wake a terminated app and deliver an event before any tracking session exists. This is why geofencing is documented separately from the lifecycle state machine: Beekon.state describes the tracking session, while geofences run on the OS’s own monitoring budget regardless of that state.