Quickstart
This page walks through install, configure, start, and observing locations on every platform. Pick your platform once — the tabs stay synced as you scroll.
The platform-specific OS configuration (manifest, plist, pubspec) is in the per-platform setup deep-dives:
- Android setup — permissions, foreground service notification
- iOS setup — Info.plist keys, background mode
- Flutter setup — both platforms’ native config
1. Install
Section titled “1. Install”dependencies { implementation("io.github.wayqteam:beekon:0.0.3")}Min SDK 26, Kotlin 2.x. Maven Central is included by default in AGP — no extra repo needed.
In Xcode: File → Add Package Dependencies… and paste:
https://github.com/wayqteam/beekon-ios-binary.gitOr in Package.swift:
dependencies: [ .package(url: "https://github.com/wayqteam/beekon-ios-binary.git", from: "0.0.3"),],targets: [ .target(name: "App", dependencies: [ .product(name: "BeekonKit", package: "beekon-ios-binary"), ]),]iOS 17 minimum. The package ships as a signed .xcframework binary target — set the framework to Embed & Sign in your app target.
dependencies: beekon_flutter: ^0.0.3flutter pub getFlutter 3.44+ recommended (default SwiftPM). On 3.32–3.43 enable it once: flutter config --enable-swift-package-manager. CocoaPods is not supported.
2. Configure
Section titled “2. Configure”configure sets the gate (interval + distance). Call it once before start(); you can call it again mid-session to live-tune the gate without restarting the location subscription.
Beekon.initialize(context) is one-time and idempotent — call it once in Application.onCreate. It wires the SDK to the application context and rehydrates persisted intent (so a process restart can resume tracking automatically).
class App : Application() { override fun onCreate() { super.onCreate() Beekon.initialize(this) Beekon.configure( BeekonConfig( intervalSeconds = 30, distanceMeters = 100.0, notification = BeekonConfig.Notification( title = "Beekon", text = "Tracking your location", smallIcon = R.drawable.ic_stat_beekon, ), ), ) }}All Notification fields are optional — leave them null to inherit the host app’s label and launcher icon. Channel id and notification id are SDK-internal constants. See Background execution for the foreground-service mechanics.
import BeekonKit
@mainstruct MyApp: App { init() { Task { await Beekon.shared.configure( BeekonConfig(intervalSeconds: 30, distanceMeters: 100) ) } } var body: some Scene { WindowGroup { ContentView() } }}Beekon.shared is an actor — every method is safe to call from any context. There’s no separate initialize(); the actor is auto-initialized. The host app drives the authorization prompt before calling start(); see iOS setup.
import 'package:beekon_flutter/beekon_flutter.dart';
Future<void> bootstrap() async { await Beekon.instance.configure( const BeekonConfig( intervalSeconds: 30, distanceMeters: 100, androidNotification: AndroidNotification( title: 'Beekon', text: 'Tracking your location', smallIcon: 'ic_launcher', ), ), );}androidNotification is honoured on Android and silently ignored on iOS. smallIcon is a drawable resource name (without the R.drawable. prefix) — it’s resolved against the host app’s res/drawable* folders at configure-time.
import { Beekon } from '@wayq/beekon-rn';
await Beekon.configure({ intervalSeconds: 30, distanceMeters: 100, androidNotification: { title: 'Beekon', text: 'Tracking your location', smallIconResName: 'ic_launcher', },});androidNotification is honoured on Android and ignored on iOS. smallIconResName is a drawable resource name resolved at runtime via Resources.getIdentifier().
3. Start tracking
Section titled “3. Start tracking”start() requests location updates and brings up the platform’s background-execution machinery (Android foreground service, iOS CLBackgroundActivitySession). It throws if permission is missing or the platform refuses.
lifecycleScope.launch { try { Beekon.start() } catch (e: BeekonError.PermissionDenied) { // request ACCESS_FINE_LOCATION + ACCESS_BACKGROUND_LOCATION, then retry } catch (e: BeekonError.LocationServicesDisabled) { // device has Location Services off, or no Google Play Services }}do { try await Beekon.shared.start()} catch BeekonError.permissionDenied { // user denied or hasn't granted Always — see iOS setup for the prompt flow} catch BeekonError.locationServicesDisabled { // Settings → Privacy → Location Services is OFF system-wide}try { await Beekon.instance.start();} on PermissionDenied { // request runtime permissions on Android, drive Always prompt on iOS} on LocationServicesDisabled { // Location Services off (or no Google Play Services on Android)}import { Beekon, BeekonError } from '@wayq/beekon-rn';
try { await Beekon.start();} catch (e) { if (e instanceof BeekonError && e.kind === 'permissionDenied') { // request runtime permissions } else if (e instanceof BeekonError && e.kind === 'locationServicesDisabled') { // Location Services off (or no Google Play Services on Android) }}4. Observe state and locations
Section titled “4. Observe state and locations”State and locations are streamed; consume with whatever’s idiomatic for your platform.
// Latest state — StateFlow replays the current value to new collectorsval state by Beekon.state.collectAsStateWithLifecycle()
// Live fixes — SharedFlow with DROP_OLDEST(64); slow consumers don't block writesLaunchedEffect(Unit) { Beekon.locations.collect { loc -> Log.d("beekon", "${loc.latitude}, ${loc.longitude} acc=${loc.horizontalAccuracyMeters ?: "—"}m") }}Task { for await state in await Beekon.shared.state { print("state: \(state)") }}
Task { for await loc in await Beekon.shared.locations { let acc = loc.horizontalAccuracyMeters.map { "\($0)" } ?? "—" print("\(loc.latitude), \(loc.longitude) acc=\(acc)m") }}Beekon.shared.locations is overloaded: as a property it’s the live AsyncStream; called as a function (locations(from:to:)) it’s a historical-range fetch (see Step 5).
StreamBuilder<BeekonState>( stream: Beekon.instance.state, initialData: const Idle(), builder: (context, snap) => Text('${snap.data}'),);
Beekon.instance.locations.listen((Location loc) { print('${loc.latitude}, ${loc.longitude} acc=${loc.horizontalAccuracyMeters ?? "—"}m');});The live stream only emits while Flutter is alive — the persistent record is the native DB. Read it via history.
const unsubscribeState = Beekon.onState((s) => console.log('state:', s));const unsubscribeLoc = Beekon.onLocation((loc) => { const acc = loc.horizontalAccuracyMeters ?? '—'; console.log(`${loc.latitude}, ${loc.longitude} acc=${acc}m`);});
// later:unsubscribeState();unsubscribeLoc();Callbacks fire on the JS thread. onLocation only emits while the JS engine is alive; for fixes captured in background, use history.
5. Read history
Section titled “5. Read history”History is the native library’s own SQLite database. It survives process death; the live stream does not. Retention is TTL 7 days + 100,000 row cap, auto-pruned every 200 inserts.
val now = Instant.now()val hourAgo = now.minus(1, ChronoUnit.HOURS)val points: List<Location> = Beekon.history(from = hourAgo, to = now)let now = Date()let hourAgo = now.addingTimeInterval(-3600)let points = try await Beekon.shared.locations(from: hourAgo, to: now)final now = DateTime.now();final hourAgo = now.subtract(const Duration(hours: 1));final points = await Beekon.instance.history(from: hourAgo, to: now);const now = new Date();const hourAgo = new Date(now.getTime() - 60 * 60 * 1000);const points = await Beekon.history(hourAgo, now);See Persistence & history for the schema.
- Wire OS-level config: Android, iOS, or Flutter.
- Read Lifecycle & states — the state machine that drives
state. - Read Background execution for what’s actually keeping your app alive.