Quickstart
Four steps to your first fix: install → configure → start → subscribe. Pick your platform once — the tabs stay synced as you read.
1. Install
Section titled “1. Install”dependencies { implementation("io.github.beekonlabs:beekon:0.1.1")}minSdk 26, JDK 17, Kotlin 2.x. Maven Central is included by default — no extra repository needed.
In Xcode: File → Add Package Dependencies… and paste:
https://github.com/beekonlabs/beekon-ios-binary.gitSelect version 0.1.1 and add BeekonKit to your app target. The package ships as a signed .xcframework — set it to Embed & Sign on your app target. iOS 17 minimum.
dependencies: beekon_flutter: ^0.1.1flutter pub getDart ^3.9, Flutter >=3.32. Android minSdk 26, iOS 17.
npm install @wayq/beekon-rnOn iOS, install pods:
cd ios && pod installRequires the React Native New Architecture (RN 0.76+). Android links automatically.
2. Configure
Section titled “2. Configure”configure is optional — call it before start() to set the gate. Without it, Beekon uses sensible defaults (30 s / 100 m, balanced, pause). Every field is documented on Configuration.
There’s no initialize(...) — the SDK auto-initializes via AndroidX Startup. Call configure once in Application.onCreate.
import in.wayq.beekon.Beekonimport in.wayq.beekon.BeekonConfig
class App : Application() { override fun onCreate() { super.onCreate() Beekon.configure( BeekonConfig.SelfManaged( minTimeBetweenLocationsSeconds = 30, minDistanceBetweenLocationsMeters = 100.0, accuracyMode = AccuracyMode.Balanced, whenStationary = StationaryMode.Pause, notification = NotificationConfig( title = "Tracking location", text = "Capturing your trip", ), ), ) }}There’s no initialize() — Beekon.shared is an actor, auto-initialized. configure is async throws.
import BeekonKit
@mainstruct MyApp: App { init() { Task { try await Beekon.shared.configure( BeekonConfig.selfManaged( minTimeBetweenLocationsSeconds: 30, minDistanceBetweenLocationsMeters: 100, accuracyMode: .balanced, whenStationary: .pause ) ) } } var body: some Scene { WindowGroup { ContentView() } }}There’s no initialize() — the native SDKs auto-initialize. configure is async.
import 'package:beekon_flutter/beekon_flutter.dart';
Future<void> main() async { await Beekon.instance.configure( const BeekonConfig.selfManaged( minTimeBetweenLocationsSeconds: 30, minDistanceBetweenLocationsMeters: 100, accuracyMode: AccuracyMode.balanced, whenStationary: StationaryMode.pause, ), ); runApp(const MyApp());}There’s no initialize() — the native SDKs auto-initialize. configure is async.
import { Beekon } from '@wayq/beekon-rn';
await Beekon.configure({ mode: 'selfManaged', minTimeBetweenLocationsSeconds: 30, minDistanceBetweenLocationsMeters: 100, accuracyMode: 'balanced', whenStationary: 'pause',});3. Start tracking
Section titled “3. Start tracking”start() brings up the platform’s background machinery (Android foreground service, iOS CLBackgroundActivitySession). On native it’s suspend/async and never throws — a missing permission or a platform refusal surfaces as Stopped(reason) on the state stream, not an exception.
lifecycleScope.launch { Beekon.start() // never throws}await Beekon.shared.start() // never throwsawait Beekon.instance.start(); // never throwsawait Beekon.start(); // never throws4. Subscribe to locations
Section titled “4. Subscribe to locations”The locations stream delivers each admitted fix. Consume it the idiomatic way for your platform.
// locations is a SharedFlow (replay 1, DROP_OLDEST)Beekon.locations.collect { loc -> Log.d("beekon", "${loc.latitude}, ${loc.longitude} acc=${loc.accuracy ?: "—"}m")}
// observe why tracking stopped, if it doesBeekon.state.collect { s -> if (s is BeekonState.Stopped) when (s.reason) { StopReason.PermissionDenied -> { /* request permission, then start() again */ } StopReason.LocationServicesDisabled -> { /* Location Services off */ } else -> {} }}Task { for await loc in await Beekon.shared.locations { let acc = loc.accuracy.map { "\($0)" } ?? "—" print("\(loc.latitude), \(loc.longitude) acc=\(acc)m") }}
// observe why tracking stopped, if it doesTask { for await s in await Beekon.shared.state { if case let .stopped(reason) = s { switch reason { case .permissionDenied: break // drive the Always prompt, retry case .locationServicesDisabled: break // Location Services off default: break } } }}// Each subscription returns an unsubscribe; on* / .listen both work.Beekon.instance.locations.listen((loc) { print('${loc.latitude}, ${loc.longitude}');});
Beekon.instance.state.listen((state) { if (state is Stopped && state.reason == StopReason.permissionDenied) { // Request permission, then start() again. }});// Each subscription returns an unsubscribe function.const offLocation = Beekon.onLocation((loc) => { console.log(loc.latitude, loc.longitude);});
const offState = Beekon.onState((state) => { if (state.kind === 'stopped' && state.reason === 'permissionDenied') { // Request permission, then start() again. }});That’s the whole loop. Fixes also land in an on-device SQLite history that survives process death — read ranges with getLocations(from, to). See Locations & history.
- Wire the OS-level config: Platform setup.
- Tune the gate: every field on Configuration.
- Send fixes to your backend — turn on sync and build the ingest endpoint.
- Read Lifecycle & states — the state machine behind
state.