tracelet 3.7.3 copy "tracelet: ^3.7.3" to clipboard
tracelet: ^3.7.3 copied to clipboard

Production-grade background geolocation for Flutter. Battery-conscious tracking, geofencing, SQLite persistence, HTTP sync, and headless execution for iOS & Android.

3.7.3 #

FIX: (Android) minified release builds no longer fall back to the AOSP location stack on devices that have Google Play services. TraceletServices.isGmsAvailable resolved GoogleApiAvailability reflectively so play-services-base could stay a soft dependency, but R8 rewrites the Class.forName string literal to the renamed class while leaving the getMethod("getInstance") argument untouched — so the class resolved, the method lookup threw NoSuchMethodException, and the catch reported GMS as missing. Field logs show it verbatim: Exception in isGmsAvailable reflection check: v2.d.getInstance [] on a Galaxy S23. Every minified build since 3.6.x was therefore running on raw LocationManager with GPS_PROVIDER + NETWORK_PROVIDER interleaved, the deprecated addProximityAlert, and a no-op activity-recognition client — feeding coarse network fixes straight into the geofence evaluator, which the accuracy-aware EXIT gating from #274/#276 cannot defend against. The probe now distinguishes "GMS absent" from "the probe could not run": a probe failure falls back to an OS package-manager query that no shrinker can rename, and a -keep rule for GoogleApiAvailability ships in both consumer ProGuard files so the precise reflective path keeps working in host apps.

FIX: geofence ENTER/EXIT transitions are now logged, at INFO, with the full decision trace on both Android and iOS. evaluateHighAccuracyProximity and the OS-transition handler previously logged nothing at any level, so a report of an occasional false EXIT produced a bug report with zero geofence content and had to be triaged from configuration alone. Each crossing now emits [geofence] EXIT <id> dist= radius= buffer= thr= margin= accRaw= accEff= exitAccuracyMax=, which is what separates a genuine departure from drift: a small accRaw with a large dist is an over-confident fix, clampApplied=true shows geofenceExitAccuracyMax binding and weakening drift immunity relative to the -1 default, and accuracyInvalid=true gatingDisabled=true flags a fix with no valid accuracy (negative horizontalAccuracy on iOS, 0.0 on Android), which the evaluator treats as zero uncertainty. The line carries distance-from-centre rather than coordinates, so it is safe to paste into an issue. The OS/AOSP path logs source=os and states that it has no accuracy gating, and updateProximity now labels its line "not ENTER/EXIT" — it reports monitoring scope, and apps that read geofencesChange.off as an exit will see phantom exits from a single far-drifting fix.

FEAT: TraceletBugReport gained a Geofence transitions (decision trace) section that lifts [geofence] lines out of the general log stream and scans geofenceTraceLimit (2000) entries rather than the 500-entry log window. Crossings are rare while lifecycle chatter is not, so in a busy app the transitions were being pushed out of the exported window before anyone generated a report.

FIX: HealthCheck.motionPermission is documented correctly and gained a typed HealthCheck.motionAuthorization getter. The field carries a MotionAuthorizationStatus index (notDetermined, granted, deniedForever → 0, 1, 2), but its doc comment described CoreMotion's CMAuthorizationStatus scale, which orders the cases differently and has a separate restricted value. Callers that implemented the documented contract — including Tracelet Doctor — reported a granted permission as "Restricted". The new getter returns the typed value (null when out of range) so the mapping cannot be misread.

PERF: the native loggers no longer run a DELETE after every log write. Retention is 500-2000 rows, so pruning is now amortized every 50 writes on both platforms.

3.7.2 #

FIX: (smart motion) the accelerometer can now contribute to a stationary decision after a start that begins in MOVING. The Rust coordinator initialises is_accel_moving = false and ignores an unchanged flag, and start() never seeded it, so the stop-timeout fired, reported stationary, and the coordinator emitted nothing — leaving the GPS-speed machine as the only input that could ever change the pace. start() now seeds the flag from the state it starts in, and also re-syncs the coordinator's tracking mode (it was previously only synced in initialize(), from the persisted mode, so a session that ended stationary could leave the coordinator unable to ever switch again) (#288).

FIX: (smart motion) MotionDetector no longer writes isMoving itself in smart mode. The accelerometer is one of two inputs there — the coordinator owns the decision — so claiming the transition locally left getState().isMoving disagreeing with the last onMotionChange event and with actual GPS behaviour whenever the coordinator decided to stay continuous (#288).

FIX: shakeThreshold, stillThreshold and stillSampleCount are no longer transmitted unless you set them, so each platform keeps its own tuned default. Dart's defaults are the Android values, and they were sent unconditionally by any app that configured any motion field: iOS converts m/s² to g, so stillThreshold: 0.4 arrived as 0.04 g — about four times stricter than the 0.15 g iOS default it was meant to keep — and stillSampleCount: 25 dwelt ~2.5 s at iOS's 10 Hz instead of the intended ~5 s. Reading these properties still reports the documented Dart defaults (2.5 / 0.4 / 25), and setting a value — even one equal to the default — is honoured on both platforms, so this is not a breaking API change. iOS's own fallback for stillSampleCount was also corrected from 30 to 50 (≈5 s at 10 Hz), matching the documented intent and Android's ~5 s dwell (#288).

FIX: a still device now reaches STATIONARY on schedule instead of being stranded in MOVING. While the speed state machine counted down in SLOWING, a single GPS fix at or above speedMovingThreshold cancelled the countdown and restarted the whole speedStationaryDelay window. GPS speed is noisy on a stationary device — an isolated 1.56 m/s blip amid a stream of 0.00 m/s fixes was enough — so the pace could keep restarting its countdown indefinitely while the accelerometer had already reported sustained stillness. SLOWING now requires three consecutive above-threshold fixes before returning to MOVING (the same sustained-motion remedy MotionDetector applies to accelerometer noise) and the countdown keeps its original start time across a blip. Safe by construction: SLOWING is still continuous tracking, so confirming over a couple of fixes costs no location fidelity. Applied on Android and iOS (#288).

FIX: the tracelet_sync sink is now process-wide instead of one per FlutterEngine. Both native plugins created a new TraceletSyncSink on every engine attach and never detached one, so any host that spawns secondary engines — workmanager creates one per background task, plus headless engines and engine groups — accumulated sinks for the life of the process. Each sink owns its own concurrency guard (a CoroutineScope + Mutex on Android, a SyncCoordinator actor on iOS), so those guards stopped serializing anything and a single persisted location fanned out into N blocking auto-syncs, each pinning one or two threads: OutOfMemoryError: pthread_create failed, heap exhaustion, duplicate points server-side and racing clearLocationsUpTo calls. The sink is now created once and reused by every later engine, and it is deliberately kept alive on detach so native/headless tracking keeps syncing after a short-lived engine goes away. On iOS the plugin also stopped subscribing the sink twice per engine (directly and through the syncProvider didSet) and gained a detachFromEngine(for:) hook (#286).

FIX: (iOS) a superseded sync provider can no longer stay subscribed to the LocationEngine. registerSink was a bare append with no dedupe and there was no way to remove a sink at all, so duplicate and stale sinks each fanned out another insertLocation for the same fix. registerSink now dedupes by identity, unregisterSink was added (Android has had both since #204), and TraceletSdk.syncProvider cancels and unregisters the provider it replaces (#286).

3.7.1 #

FIX: locationSource and reducedAccuracy are no longer dropped from persisted and synced locations. Both fields are emitted on the live onLocation event, but the persist path only serialized audit_*, battery and extras into the route_context column, so the classification was lost at write time — every DB-sourced read (getLocations, getPendingLocations) and the DB-sourced sync payload (setSyncBodyBuilder) reported locationSource: "unknown" / reducedAccuracy: false. This broke the documented guidance to filter historical/synced fixes by locationSource == "gps". Both fields are now persisted as first-class route_context keys (like audit_*) and promoted back to the top level by LocationMapper on read; because the Pigeon TlLocation boundary has no dedicated fields for them, they are carried across it via extras (like the live event) and unpacked by Location.fromMap, so the live event and DB-sourced reads agree. Applied on Android and iOS (#280).

FIX: (iOS) high-accuracy periodic fixes are no longer a single requestLocation() one-shot, which frequently returned a stale cached or first-coarse fix before the GPS hardware converged (persisting a Wi-Fi/cell-level fix for a periodic tick). When periodicDesiredAccuracy is DesiredAccuracy.high, performPeriodicFix() now routes through the same best-of-N sampling window getCurrentPosition already uses (collectSamples → most-accurate sample), bounded by locationTimeout, so periodic fixes are GPS-quality. Non-high periodic accuracy keeps the cheaper single-shot path, and overlapping ticks are guarded against (#282).

3.7.0 #

FEAT(geofence): accuracy-aware geofence EXIT for high-accuracy mode. A circular geofence now only fires EXIT once the entire GPS error circle clears the fence (distance - accuracy > radius + buffer), so a single high-drift, low-confidence fix no longer produces a false EXIT while a device is stationary inside a small geofence. ENTER stays accuracy-agnostic so arrivals still trigger promptly (#274).

FEAT(geofence): new GeofenceConfig.geofenceExitAccuracyMax (meters) to tune the accuracy-aware EXIT gating — -1 full gating (default, most drift-resistant), 0 disables gating (fastest, most eager EXIT), and N > 0 clamps the accuracy used in the exit test to N to bound the worst-case exit delay while still absorbing drift up to N. High-accuracy path only; no effect in standard OS region-monitoring mode (#276).

3.6.15 #

FIX: (Android) geofence transitions and confirmed crash/fall deliveries could be silently dropped right after a cold boot. Since #260 moved the heavy initialize() setup (Rust DB open, lateinit geofenceManager/engines) onto a background thread, initialize() returns before those managers exist. #264 guarded the ready() / bootstrapForBackground() paths, but the native broadcast entry points still raced init: GeofenceBroadcastReceiver read geofenceManager immediately after initialize() (so a cold-boot ENTER/EXIT — a trip start — was swallowed and lost), and CrashConfirmReceiver delivered a confirmed impact onto not-yet-wired state. Both now funnel through a new awaitInit() gate that blocks until init completes (or reports failure/timeout) before touching those managers, so the transition/impact is delivered instead of dropped. iOS is unaffected (its initialize() is synchronous) (#271).

3.6.14 #

FIX: geofence ENTER/EXIT flapping for a stationary device inside the radius (high-accuracy mode). The evaluator used a single distance <= radius threshold for both entry and exit, so a motionless device whose GPS fixes jittered across the boundary emitted repeated ENTER/EXIT events. Exit now applies hysteresis — the device ENTERs at the true radius but only EXITs once it is farther than radius + max(radius * 0.1, 20 m) from the center — so boundary jitter no longer flips the state. Applied in both the pure-Dart evaluator (the active high-accuracy path) and the Rust core used by the native SDKs (#268).

FEAT: (Android) add Tracelet.requestTermination() to stop the GPS foreground service from a headless Dart isolate. When an FCM silent push runs a background task while the app is terminated, Tracelet.stop() is unavailable because it relies on Pigeon, which headless isolates cannot reach — so the foreground service kept polling and draining battery until the app was reopened. A new requestTermination handler on the com.tracelet/methods MethodChannel (registered on the headless FlutterEngine) calls TraceletSdk.stop(), letting background handlers shut tracking down cleanly (#267).

3.6.13 #

FIX: (Android) prevent a runtime crash when com.google.android.gms:play-services-location resolves below 21.2.0. Tracelet's Android bytecode calls the interface-based FusedLocationProviderClient and ActivityRecognitionClient APIs, which only became interfaces in play-services-location 21.2.0. When a host app resolved an older version (e.g. 19.0.0) transitively, those types were still concrete classes, so calling into them threw java.lang.IncompatibleClassChangeError (crashing the periodic location worker and, after permission handling, the main thread). play-services-location stays compileOnly, so the SDK still degrades gracefully to the AOSP LocationManager when GMS is absent; a published Gradle dependency constraint now raises the resolved version to a compatible floor (>= 21.2.0) whenever the dependency is present, without adding it to the dependency graph (#263).

FIX: (Android) prevent a boot/restart crash with UninitializedPropertyAccessException: lateinit property geofenceManager has not been initialized. With startOnBoot: true and stopOnTerminate: false, LocationService.startBootTracking() called bootstrapForBackground() and then immediately accessed the geofenceManager. Since 3.6.9, initialize() runs on a background tracelet-init thread and bootstrapForBackground() did not wait for it, so on a cold boot the lateinit managers could still be unassigned — crashing the service in onStartCommand before the foreground notification was posted (a timing race most reliably seen on slower environments such as emulators). bootstrapForBackground() now blocks on the init latch and returns a success flag: it preserves the initialization exception (no longer mistaking a released latch for success) and verifies the Rust DB and geofenceManager are actually assigned. startBootTracking() defers gracefully without touching any manager, and PeriodicLocationWorker returns Result.retry(), when initialization has not completed (#264).

FIX: (Android) Tracelet.addGeofence() no longer returns false for a geofence that was actually registered. When no device location is known yet, addGeofence() persists the record and calls registerGeofence(), whose Google Play Services registration is asynchronous. The Flutter host calls this on the main thread, where the SDK correctly does not block on the registration callback — but it then returned the still-false result before the callback ran, so apps saw a bogus failure even though getGeofences() listed the geofence. On the main thread the SDK now returns true once the registration request has been scheduled without a synchronous error (off the main thread it still awaits the real callback result); genuine Play Services failures continue to be logged. iOS and web were unaffected (#265).

3.6.12 #

FIX: Tracelet.ready() no longer surfaces remote-config event registration failure as an uncaught async error. Since 3.6.10, ready() subscribes to remoteConfigEvents, which lazily registers the Pigeon event channel and fired requestStateFlush() fire-and-forget. When the platform side was unreachable (e.g. a headless flutter test with no channels, or a temporarily detached engine), the rejected future became an uncaught async error routed to the zone error handler instead of one the caller's await ready(...) could catch — fatal for a ride-start path that wrapped ready() in try/catch and still got torn down. The best-effort flush is now awaited inside a guarded helper that contains any failure, so it can never escape as an uncaught async error; event registration itself already succeeded, so nothing observable is lost and callers can always recover (#262).

3.6.11 #

FIX: (iOS) IosConfig.useSignificantChangesOnly no longer keeps the persistent system location indicator on. On iOS 17+, enabling significant-change monitoring and calling Tracelet.start() still showed an ongoing location indicator (Dynamic Island / status-bar pill) because start() opened a CLBackgroundActivitySession whenever the device was moving, even though continuous GPS was correctly skipped. CLBackgroundActivitySession holds a background location activity alive and auto-shows the indicator, defeating the whole point of significant-change monitoring (low-power background location with no persistent indicator). The SDK now fully honors significant-changes-only mode — it neither opens a CLBackgroundActivitySession nor starts continuous GPS (startUpdatingLocation), which independently light up the system location indicator — across start(), the motion-detection pipeline's switch-to-continuous, changePace transitions, and killed-state auto-resume, matching the existing behaviour of periodic mode and low-accuracy geofence-only mode. High-accuracy geofencing and the explicit IosConfig.useBackgroundActivitySession opt-in are unaffected. The indicator may still blink briefly when a significant-change event is delivered, which is normal iOS behaviour. (#261)

3.6.10 #

FIX: Remote configuration overrides (Enterprise remoteConfigUrl) now propagate to the Dart layer. Remote config is fetched and applied entirely on the native side; previously the result never crossed back to Dart, so Tracelet.activeConfig — and anything reading it, such as tracelet_doctor and the Dart-side battery-budget engine — kept showing the last locally-set values (e.g. a remotely fetched batteryBudgetPerHour of 1.0 never appeared, while a local setConfig value did). The native layer now emits an onRemoteConfig event whenever it applies a remote override — both the freshly fetched config and the cached copy restored at ready() — and the Dart layer folds it into the active config, re-initialising the Dart-side battery-budget engine. A new Tracelet.onRemoteConfig(...) callback and Tracelet.remoteConfigStream let apps react to server-driven configuration changes.

3.6.9 #

FIX: Remote config (and any runtime setConfig()) now applies batteryBudgetPerHour. The battery-budget engine was only built during ready(), so a remote-config push such as {"geo":{"batteryBudgetPerHour":1.0}} delivered at runtime via setConfig() was stored but never acted on — it only appeared to work after a cold restart (which applies the cached copy before ready() builds the engine). The engine is now rebuilt when batteryBudgetPerHour changes at runtime on both Android and iOS, and battery-budget sampling is started or stopped to match the live tracking state.

FIX: iOS — all Double configuration getters now read through NSNumber, so integer-encoded values (e.g. 1 instead of 1.0 from a remote-config JSON endpoint, or a plain Swift Int) coerce correctly instead of silently falling back to their defaults. This matches the existing Android coercion behaviour.

FIX: Android — initialize() now runs its heavy setup (opening the Rust database, which fsyncs to disk) on a background thread instead of the caller's main thread, and ready() waits for it to finish. Previously, when the system re-created a background FlutterEngine (e.g. audio_service's media service after the app was killed), GeneratedPluginRegistrant re-attached the plugin and the disk fsync ran on that service's main thread, causing an ANR on databases grown large over days of tracking. Thanks to @dagovalsusa (#260).

3.6.8 #

FEAT: Expose Tracelet.updateNotification(), a public API to refresh the active Android foreground-service notification after changing its configuration (#257). The foreground-service notification is configured through ForegroundServiceConfig (title, text, icon, color, actions, priority, ongoing state), but there was previously no public way to apply notification-only changes to an already-running service — a notification-only setConfig() did not repost the live notification, so new content only appeared after an unrelated service restart or foreground transition. updateNotification() now refreshes the active on-screen tracking indicator from the latest configuration without restarting the tracking pipeline. On Android the ACTION_UPDATE_NOTIFICATION service path rebuilds and reposts the foreground-service notification (previously a no-op) when the service is promoted, and is a safe no-op when the service is not running. iOS has no foreground-service notification, so updateNotification() instead refreshes the running Live Activity — when the app opted into one via liveActivityConfig — from the latest config (the dynamic body; the title is immutable on a running activity), and is a safe no-op otherwise. Web is a no-op.

3.6.7 #

FIX: In MotionDetectionMode.smart / .speed, setConfig() could restore a temporary stationary mode as the main tracking mode. Those modes run a single continuous motion-aware pipeline that temporarily flips the tracking mode to periodic/geofences while the device is stationary. A restart-sensitive setConfig() captured that temporary tracking mode and rebuilt the pipeline via the standalone startPeriodic()/startGeofences() paths, tearing down the motion-detection pipeline that switches back to continuous on movement — stranding tracking in a standalone stationary mode. setConfig() (and, on iOS, ready()'s resume path) now restarts the continuous motion-aware pipeline via start(isResume: true) whenever the motion-detection mode is smart/speed, regardless of the temporary tracking mode; the pipeline re-enters the stationary sub-state on its own when still stationary. Fixed on both Android and iOS (#256).

3.6.6 #

FEAT: Added Tracelet.getForegroundServiceHealth() — exposes the authoritative native foreground-service state (whether the service is running and promoted to the foreground, the last promotion result of success/deferred/failed with its failure class and message, the notification id, and the last transition timestamp) alongside the desired enabled state. On Android 12+ a foreground-service start can be deferred or rejected by the OS even while tracking is enabled, so enabled alone is not proof that background tracking is operational; this lets apps build accurate tracking-health indicators, diagnostics, and recovery. iOS reports the desired state with null/false promotion fields (it has no foreground service), and web returns a minimal disabled map (#255).

FIX: On Android, changing a restart-sensitive setting via setConfig() while tracking with a foreground service could kill that service. The restart path called the full stop() — which sends ACTION_STOP to LocationService (stopForeground + stopSelf) — and immediately restarted the pipeline with ACTION_START. On a fresh promotion the ACTION_STOP handler's stopSelf() could win the race and destroy the service right after ACTION_START promoted it, leaving no foreground service at all — the same race fixed for startPeriodic() in #237. stop() now accepts a preserveForegroundService flag and the setConfig() restart path keeps the service alive whenever the target mode still needs it, letting the idempotent ACTION_START re-assert foreground with no gap; modes that do not use the service stop it cleanly with no follow-up start to race. iOS is unaffected (#254).

3.6.5 #

FIX: On Android a failed foreground promotion no longer leaves the service marked as a running foreground service. LocationService.startForegroundWithNotification() catches a startForeground() failure and tears the service down (stopForeground + stopSelf + isRunning = false), but because the exception was swallowed, execution returned normally and every caller then set isForegroundService = true unconditionally. The method now returns whether the promotion succeeded and all callers gate isForegroundService on that result, so a failed promotion leaves the flag false. iOS is unaffected — it has no foreground-service promotion that can fail after the fact (#253).

3.6.4 #

FIX: On iOS the heartbeat writer no longer persists a GPS fix that the normal dispatch already stored, so getLocations() no longer returns byte-identical duplicate location rows (roughly half the points of a moving trip were duplicated on-device). The normal dispatch persists with event="location" and the heartbeat timer re-tagged the same cached fix with event="heartbeat" and inserted it again; the dedup guard only skipped repeats for event="location", so the heartbeat write slipped through. The guard now shares one last-inserted-timestamp key across both writers. The Android guard is kept in parity (#252).

3.6.3 #

FIX: destroyLocation(uuid) now deletes the record addressed by its public UUID on both Android and iOS. Previously both native SDKs parsed the UUID string as a numeric database id (toLongOrNull() / Int64(uuid)), so any real UUID failed to parse and the call returned false without deleting anything — pending locations could never be acknowledged and the queue never drained. The UUID is now resolved to its row id before deletion, with the legacy numeric-id path kept for backward compatibility (#251).

FIX: IosConfig.activityType is now applied to CLLocationManager as configured on iOS. Two independent bugs previously made every value resolve to .otherNavigation: the Dart bridge mapped between two differently-ordered enums by raw index (so e.g. otherNavigation was sent as fitness), and the native side stored the value as an Int but read it back as a String and always fell through to the default. Both sides now agree, so automotiveNavigation/fitness/airborne take effect (#250).

3.6.2 #

FEAT: Remote config (remoteConfigUrl) is now fetched and applied natively on iOS and Android. On ready() the SDK fetches a JSON config map from your HTTPS endpoint, applies it over the local config (restarting the tracking pipeline when a tracking-relevant key changes), and refreshes it in the background on the remoteConfigRefreshInterval cadence. The last successful response is cached to disk, so a restart resumes on the freshest known settings instantly and offline. Only HTTPS URLs are honored. Previously both platforms recognized the field but never fetched it — the native side silently fell back to the local config.

FIX: Stop double-inserting stationary periodic fixes with the same uuid. The stationary periodic timer in LocationService now passes persist=false to getCurrentPosition() so it stays the single writer of the enriched "periodic" record (and the single event dispatch). Fixes the "UNIQUE constraint failed: location_events.uuid" error that occurred every stationary tick (#248).

3.6.1 #

FIX: Explicit GeofenceConfig(geofenceModeHighAccuracy: false) is now honored on aggressive OEMs (Samsung/Xiaomi/Huawei/OnePlus/Oppo/Vivo) instead of being silently forced to true — which made startGeofences() start the location engine and the LocationService foreground service with its persistent notification, the exact thing low-accuracy geofences-only mode exists to avoid (and which Google Play prohibits solely for geofencing from 2026-10-28). Consistent with the #243 fix, the configured value is authoritative on every device and the SDK logs a reliability warning instead (#247).

3.6.0 #

FEAT: Tracelet.updateLocationProviderOptions() — temporarily override desiredAccuracy/distanceFilter on the running OS provider without a pipeline restart; ephemeral (cleared by stop()), persisted config untouched. Live on iOS (CLLocationManager property update) and Android (callback-preserving fused re-subscription) (#241).

FIX: Explicit foregroundService.enabled: false / periodicUseForegroundService: false are now honored on aggressive OEMs (Xiaomi/Huawei/Samsung/OnePlus/Oppo/Vivo) instead of being silently forced back on with the default foreground notification; the SDK logs a reliability warning instead. Leftover foreground services are also torn down when switching to a no-service periodic strategy, and sticky service restarts re-validate state/config before re-posting the notification (#243).

FIX: rejectMockLocations now guards every Android delivery path — getCurrentPosition() (including the last-known fallback), watchPosition(), and periodic fixes — not just continuous tracking (found auditing #243).

FIX: iOS buildLocationMap hardcoded activity: {type: "unknown", confidence: -1} on every persisted/dispatched location, dropping the classified transport mode even with fusedClassifierAuthoritative: true. Per-point activity now carries the effective mode and confidence (fused when authoritative, scaled 0–100; otherwise platform Activity Recognition), including on the dead-reckoning path, and Android pairs the authoritative fused type with the fused confidence instead of the unrelated AR confidence (#244).

FIX: Fused transport modes are persisted in the Activity Recognition vocabulary on both platforms (vehiclein_vehicle, cyclingon_bicycle), and the Dart Location.activity.type parser now accepts the native snake_case strings — in_vehicle/on_bicycle/on_foot previously collapsed to ActivityType.unknown (follow-up to #244).

FIX: activity.confidence now survives the DB round-trip — new activity_confidence column in the location store (auto-migrated; -1 for rows persisted before the column existed), stored on every insert including encrypted payloads, and returned by getLocations() and the sync-interceptor sink instead of a hardcoded 100 (#245).

3.5.7 #

FIX: Build fails without AGP built-in Kotlin (AGP <9 / builtInKotlin=false) (#239).

3.5.6 #

FIX: Custom sync body 400 Bad Request HTTP errors now gracefully return fallback results instead of propagating fatal exceptions in native Sync engines (#238).

3.5.5 #

FIX: Ensure foreground service is properly started in periodic mode when configured (#237).

3.5.4 #

FIX: Enrich geofence transition events with real coordinate metrics (accuracy/speed/heading/altitude) from the last GPS fix and attach the battery snapshot, instead of hardcoded zeros (#231). FIX: Propagate runtime setConfig changes to the active native tracking/sensor loops by performing a clean full-pipeline restart (location + motion/speed) when a tracking-relevant key changes (#230). FIX: Null-guard subsystems in destroyAll() so engine/Activity teardown never throws when the SDK was never initialized (fatal Unable to destroy activity) (#227). FIX: Android: standard geofence mode no longer starts a foreground service, complying with Google Play's policy (effective 2026-10-28) that prohibits using a foreground service solely for geofencing. Native geofences keep firing while the app is suspended/terminated; geofence-only apps can remove FOREGROUND_SERVICE_LOCATION from their manifest.

3.5.3 #

FIX: Added explicit ProGuard keep rules for TraceletStartupProvider in the tracelet_android package to prevent ClassNotFoundException on process start when aggressive shrinking (like R8 full mode) is used (#228).

3.5.2 #

FIX: Android continuous tracking no longer silently stops after a while on aggressive OEMs (Samsung One UI, etc.). The foreground-service wakelock used a fixed 10-minute auto-expiry and was never renewed, so once it lapsed the CPU could deep-sleep and FusedLocationProvider stopped delivering updates with no error or callback. The wakelock is now renewed for the lifetime of tracking (#222).

3.5.1 #

FEAT: Crash detection now uses the device barometer as an extra confirmation clue — a serious crash or airbag deployment causes a quick cabin air-pressure change, which raises crash confidence on phones that have a pressure sensor. Phones without one simply skip this check, with no downside (#173). FEAT: Crashes are now corroborated by a sudden post-impact speed collapse — when the vehicle goes from fast to nearly stopped in the seconds right after the jolt, crash confidence is raised. It only ever adds confidence, never cancels a real crash (#181). FEAT: Falls are now corroborated by the classic free-fall → impact → stillness signature — a brief weightless drop followed by the body coming to rest raises fall confidence (#180). FEAT: Crash/fall confirmation is now process-death-safe — if the OS kills the app during the cancel countdown (phone thrown, vehicle at rest, Doze), the confirmed event is still delivered from a re-armed wake-up (#182). DOCS: Rewrote the Driving & Safety crash/fall confirmation section in plain, beginner-friendly language.

3.5.0 #

FEAT: Crash-detection ML model promoted from beta to stable — the shipped model is trained on the CC0 / public-domain Smartphone IMU Road Accident Detection dataset, so it is cleared for commercial use in production apps (#183). FEAT: The on-device encrypted model cache now auto-re-downloads when a new model version is published (SHA-256 of the cached blob no longer matches the expected digest), so model upgrades roll out in the same session instead of falling back to the rule engine for a cycle. FEAT (example): Driving & Safety page now shows a live crash-model download/load status indicator, a "Crash (ML model)" debug inference path, a "Benign bump" demo, and a bench "Throw-test" mode. PERF: Per-window crash-model probability is now logged for on-device observability.

3.4.2 #

  • REFACTOR: reformat test files and sync body context for consistent code style. (5552f795)

3.4.1 #

  • FEAT(geofence): cross-platform geofenceModeHighAccuracy via GeofenceConfig. (491d5b83)
  • DOCS: add Discord community invitation link to READMEs and website documentation. (c3baa1c3)

3.4.0 #

  • FEAT(ios): Live Activity for active tracking — a Lock Screen & Dynamic Island indicator backed by ActivityKit, layered over the standard background pipeline (no redundant second location stream) (#202).
  • FIX(ios): resolve release-mode launch crash (SIGTRAP) caused by a duplicate key in the default config, plus a Widget Extension availability gate that hid the Live Activity and could crash the extension on iOS < 18.
  • FIX: per-call extras passed to getCurrentPosition(extras:) / getLastKnownLocation(extras:) are now forwarded to native and merged with the global HttpConfig.extras into the synced payload — the platform layer previously dropped per-call extras and desiredAccuracy (#201).
  • FIX: getCurrentPosition now defaults to high accuracy and never silently runs at passive priority, which could fail an explicit one-shot request with LOCATION_FAILURE.
  • FIX(android): a single location batch is now uploaded exactly once — duplicate sync providers no longer fire requestSyncBody twice for the same batch, preventing duplicate server uploads and duplicate DB rows (#204).
  • REFACTOR: extract issues 185 and 198, fix iOS config mapping. (1d088e0d)
  • FIX: resolve accuracy priority mappings in Android and iOS. (65f5127d)

3.3.4 #

FIX: resolve battery and extras DB persistence (#175)

3.3.3 #

  • FIX(android): deliver headless geofence events after reboot in high-accuracy mode (#185). (b197dc5f)

3.3.2 #

  • FIX (Location data, Android/iOS): Several location-map fields surfaced as static/default values in the Dart layer because the native-map → platform-channel converters dropped or mis-keyed them (#175):

    • getCurrentPosition(extras:, desiredAccuracy:) were silently ignored on Android (never forwarded to the SDK) — now applied.
    • battery.isCharging was always false — the converter read isCharging instead of the native snake_case is_charging.
    • isMoving was always false — read isMoving instead of native is_moving.

    Converters now read the native keys (with camelCase fallback), and field-by-field regression tests over the converters were added on both platforms to prevent recurrence.

  • TUNE (Crash detection): Lowered the default crashGThreshold from 3.0 g to 2.0 g. Validation against the large VZCrash field dataset showed the 3.0 g speed-gated rule missed ~48% of real crashes (median impact ~2.2 g) while the false-positive budget was small. Crash detection is opt-in with a cancel-countdown, so the default now favours recall — raise it if you see too many prompts. See #173. (Crash detection remains beta pending first-party field validation.)

3.3.1 #

  • FIX (Crash detection, Android/iOS): Confirmed crash/fall events are no longer lost when tracking stops right after the impact (the common crash → vehicle-at-rest → stopTimeout case). The confirmation countdown now runs independently of tracking state and self-terminates when no candidate is pending (#169).
  • FIX (Crash detection): The effective crash g-threshold matched the documented value — the confidence gate previously raised a 3.0 g threshold to ~3.6 g, increasing false negatives (#170).
  • FIX (Crash detection): A single crash (primary spike + bounce/secondary impacts) no longer raises multiple candidates; a refractory period debounces one event into one prompt (#171).
  • IMPROVE (Crash detection, Android/iOS): When crash/fall detection is enabled, the accelerometer is sampled at a higher rate (Android SENSOR_DELAY_GAME + no batch latency; iOS 100 Hz) so short impact peaks (~50–150 ms) are actually captured instead of missed between motion-detection samples (#172). Roadmap for research-grade robustness (Δv, sensor fusion, free-fall signature, process-death survival): #173.

3.3.0 #

  • FIX (Audit, Android/iOS): The tamper-proof audit chain only covered locations that flowed through the foreground location dispatcher. Background/headless persists (periodic worker, location service, killed-state relaunch, geofence events) wrote location rows with no matching audit-trail link, so getAuditProof() returned null for those records even with audit enabled. Audit links are now generated at the single persistence chokepoint, so every persisted location is chained regardless of source. Chain mutation is also now thread-safe.
  • FIX (Audit, iOS): appendToChain no longer creates an audit row for records without a uuid (it previously used an empty string). Such orphan rows had no retrievable location and made verifyAuditTrail() report the whole chain as broken ("missing location record"). uuid-less records are now skipped on both platforms. The audit hash version was bumped (auto-resets any orphaned/incomplete chains from the prior logic on first launch).
  • FEAT (Battery, Android): Motion-gated wakelock — drop the OEM partial wakelock when stationary and re-assert it on movement, via AndroidConfig.releaseWakelockWhenStationary (opt-in, default off; gated on the hardware significant-motion wake sensor) (#162).
  • FEAT (Driving & Safety): On-device driving-behavior telematics — harsh_braking / harsh_acceleration / harsh_cornering / speeding via TelematicsConfig + Tracelet.onDrivingEvent (opt-in, default off) (#163).
  • FEAT (Driving & Safety): On-device transport-mode classifier (still/walking/running/cycling/vehicle) fusing accelerometer + GPS via ClassifierConfig + Tracelet.onModeChange (#164).
  • FEAT (Driving & Safety): Crash & fall detection with a cancel-countdown confirmation flow via ImpactConfig + Tracelet.onImpact and Tracelet.confirmImpact / Tracelet.cancelImpact (opt-in, default off) (#165).
  • All three features are default-off and side-channel — no change to existing tracking when disabled. See Driving & Safety.

3.2.19 #

CHORE: version bump for patch release

3.2.18 #

  • FIX (Native): ready() / getState() now populate State.config with the active configuration instead of leaving it permanently null (#147).
  • FEAT: Add HttpConfig.syncInterval for interval-based sync — the documented repeating-timer cadence was missing from the Dart config and the Pigeon layer; the native interval timer now flushes the offline queue on this cadence (#149).
  • FIX (Native): destroySyncedLocations() returns the real number of synced-and-pruned locations instead of a hardcoded 0 stub (#154).
  • FEAT: Expose the offline queue with getPendingLocations() and getPendingLocationCount() (#159).
  • FIX (Native): Honor the useKalmanFilter config key so the Extended Kalman Filter is no longer silently disabled by a key mismatch (#148).
  • FIX (Native): Propagate the detected activity (walking / driving / still) into recorded locations — fixes a permanent "activity": "unknown" (#155).
  • FIX (Native): Rebuild the native location processor when ready() applies a new config, so settings such as distanceFilter take effect immediately instead of using stale defaults (#157).
  • FIX (Native): getCount() honors time-bound queries instead of always returning the whole-database total (#152).
  • FIX: Guard the AuditConfig hash-algorithm mapping so configuring sha384 / sha512 no longer crashes with a fatal RangeError during ready() — unsupported variants fall back to sha256 (#150).
  • FIX (Native): The HTTP sync payload now includes each point's motion state is_moving (#151) and its trigger event (location / motionchange / heartbeat / geofence) (#156) — both were previously omitted by the native sync record.

3.2.17 #

  • FIX (Native): Resolve iOS auto-sync thread starvation by offloading synchronous HTTP requests to a background DispatchQueue to prevent blocking Swift Concurrency pools (#146).
  • CHORE (Docs): Fix Nextra changelog rendering bug and improve auto-translation glossary script for internationalization.

3.2.16 #

  • FIX (Native): Resolve Android/iOS getting stuck in the moving state and never transitioning back to stationary, which kept continuous GPS active and drained the battery. The accelerometer stillness sampler now stays active during the stop-timeout countdown and requires sustained motion — rather than a single noisy or stale sample — to abort it (#142).
  • FIX (Native): Background and post-reboot location captures are persisted (and therefore synced) again. Headless tracking (killed-state relaunch / boot) never calls ready(), so an internal readiness guard silently dropped every captured location before it reached the database, leaving auto-sync with nothing to upload.
  • FIX (Android): The foreground-service notification now reliably appears when the app is backgrounded or terminated with showNotificationOnPauseOnly enabled. The app's own foreground service skewed foreground/background detection (and OS process-importance updates lag), so the pause-only notification was suppressed even though tracking and syncing continued.

3.2.15 #

  • FIX (Native): Allow getState() and stop() to be called before ready() is invoked, correctly reporting persistent state and shutting down background services if the app was restarted from a killed state.
  • CHORE: Update dependencies and constraints.
  • FIX: Resolve MissingPluginException and test timing issues with setHasCustomSyncBodyBuilder.

3.2.14 #

  • FIX(sync): keep method channel alive to avoid iOS timeout bugs when no builder is registered. (9a083478)
  • FIX(sync): resolve issue 134 where custom sync body timeouts prevented background syncs. (7fa16fdf)
  • FIX(sync): fix background auto-sync abortion when no custom builder is registered (Issue #134). (631542a1)
  • DOCS: add official documentation URL to all package READMEs. (9eb6951e)
  • DOCS: integrate nextra website and update pubspec URLs. (99b7fda8)

3.2.13 #

  • FIX(android): startOnBoot now resumes tracking after a reboot on devices where the OS refuses to start a location foreground service from BOOT_COMPLETED (e.g. Android 14). Previously tracking silently never resumed after a reboot; it now falls back to background WorkManager/alarm tracking.
  • FIX(android): HTTP sync now works headlessly after a reboot — background sync can refresh an expiring auth token and build a custom sync body without the app being opened. Previously the headless Dart sync bridge was only wired when a UI engine attached, so post-reboot sync used a stale token (or the wrong payload) until the app was launched.

3.2.12 #

  • CHORE: Re-release to align the full federated package set and native SDKs to a single consistent version. The 3.2.11 release published with mismatched versions across some packages (a few resolved to 3.2.10). No functional code changes.

3.2.11 #

  • FIX: Custom sync-body builder now falls back to the headless engine on timeout (instead of aborting the sync) on both Android and iOS — fixes location sync stopping after a few minutes in the background when using setSyncBodyBuilder (Issue #134).

3.2.10 #

  • FIX: streamline geofence event payload handling in fromMap method.
  • FIX: ensure geofence action (ENTER/EXIT/DWELL) is correctly parsed from nested payloads on all platforms and update CI to scan dynamic frameworks for symbols.

3.2.9 #

  • FIX(ios): Remove TraceletCore+Dummy.swift / TraceletSyncFFI+Dummy.swift@_silgen_name declarations from the old static library model caused "Undefined symbol" linker errors after the static→dynamic xcframework migration.
  • FIX(android): Catch ForegroundServiceStartNotAllowedException in LocationService.start() so calling ready() from the background on Android 12+ no longer crashes the host app; the foreground service start is deferred until the app returns to foreground.

3.2.8 #

  • FIX: Persist geofence ENTER/EXIT events in offline queue and auto-sync to server — events were previously dispatched to the app but never stored in the local SQLite database (Issue #128).
  • FIX: Structured event envelope (event_type, event_payload) for geofence events round-trips correctly through getLocations() and insertLocation().
  • FIX(sync): Stop POSTing malformed error payloads on failed HTTP sync requests; fix iOS custom-body deadlock in setSyncBodyBuilder (Issue #125).
  • FIX(android): Throw NOT_READY error before ready() is called to match iOS parity; previously Android silently ignored SDK calls before initialization (Issue #129).
  • FIX(ios): Resolve flutter_rust_bridge has not been initialized on release builds — TraceletCore is now a dynamic framework, preventing dead-code stripping of FRB symbols (Issues #116, #123, #124).
  • FIX(android): Resolve Failed to lookup symbol 'frb_get_rust_content_hash' — Rust symbols are now loaded directly from libtracelet_core.so bypassing RTLD_LOCAL isolation (Issues #116, #123).
  • PERF(ios): Reduce background motion sensor CPU/battery usage — accelerometer polling is now paused when stationary (Issue #130).
  • FIX: Persist historical is_moving state per location record so getLocations() returns accurate values instead of always returning the current live state (Issue #126).

3.2.7 #

  • FIX(ios): prevent dead code stripping of flutter_rust_bridge symbols in release builds.
  • FIX(android): implement OEM hardening mitigations and introduce showPowerManager to handle aggressive battery restrictions on specific OEM devices.

3.2.6 #

  • PERF: Optimize database timestamp queries for O(log N) fast filtering and resolve precision bugs (Issue #119).
  • FEAT: Implement sslPinningFingerprints natively across iOS and Android with Rust configs.
  • FIX: Include pinned fingerprints in SSL verification error logs and messages.
  • FIX: Rate limit Android MotionDetector logcat flooding during stillness (Issue #121).
  • FIX: Resolve race conditions in tests for Issue 118.
  • REFACTOR: Update integration test to use Config.fromMap for comprehensive Tracelet configuration testing.

3.2.5 #

  • FIX: Resolved iOS accelerometer sensitivity mismatch (stationary lock) by normalizing incoming m/s² thresholds to g-force expected by CMMotionManager.
  • FIX: Unify motion detection initial state and resume behavior across Android and iOS, preventing incorrect forced states on app launch and correctly resuming saved states.
  • FIX: Resolved flutter_rust_bridge dynamic library load failures on release builds for users without use_frameworks! by preserving global symbols during Xcode stripping.

3.2.4 #

  • FIX(ios): safely resolve dynamic symbols when use_frameworks! :linkage => :dynamic is used.

3.2.3 #

  • FIX: Force speed motion manager to evaluate initial speed on Android to prevent the state machine from being permanently stuck in MOVING when indoors (#115).
  • FIX: Resolve flutter_rust_bridge has not been initialized crash by ensuring the Rust core is instantiated and initialized before accessing methods (#116).
  • CHORE: Sync release versions across all packages.

3.2.2 #

  • CHORE: Sync release versions across all federated packages and update Swift Package Manager configuration.

3.2.1 #

  • CHORE: Align federated package versions and include additional patch updates.

3.2.0 #

  • FEAT: Added autoSyncDelay to HttpConfig — configure the debounce delay in milliseconds before automatically dispatching an HTTP sync request after a location is recorded.
  • FEAT: Introduced new tracelet_sync package for offline SQLite persistence and automatic HTTP synchronization.
  • FEAT: Add reverse geocoding (resolveAddress) functionality for automatic address lookups.

3.1.14 #

  • FIX(ios): prevent dead code stripping of flutter_rust_bridge symbols in SPM apps by referencing them explicitly in TraceletIosPlugin

3.1.10 #

  • Bump "tracelet" to 3.1.10.

3.1.9 #

  • FIX(android): conditionally apply kotlin-android plugin to support older flutter SDKs while preventing warnings in modern Flutter environments.
  • CHORE(ci): add strict pre-publish flutter build verification step to release.yml.

3.1.8 #

  • Fix iOS SPM publishing

3.1.7 #

  • FIX(android): apply kotlin-android plugin to fix gradle build errors on newer AGP versions.
  • FIX(ios): fix SPM source folder paths in release bundling to ensure SDK compiles properly via CocoaPods.
  • FIX(ios): fix duplicate module import errors by adding conditional import checks for TraceletSDK.

3.1.4 #

FEAT: Major architectural upgrade: Unified Rust Core.

  • The heavy lifting for Geofences, Privacy Zones, Audit Trail, and SQLite persistence has been moved to a shared Rust core (tracelet_core).
  • Guarantees 100% mathematical and behavioral parity between iOS and Android.
  • Eliminates subtle cross-platform inconsistencies in geofence ray-casting and proximity evaluation.
  • Native SDK wrappers (Swift/Kotlin) have been thinned out to act purely as FFI bridges via UniFFI.

FEAT: Introduced explicit predefined tracking profiles: Config.highAccuracy(), Config.balanced(), and Config.lowPower() to simplify setup.

CHORE: Release strategy overhaul. The iOS Rust Core is now bundled directly into the tracelet_ios plugin for pub.dev publication, while the Android SDK continues to be distributed via Maven Central.

3.0.1 #

  • FIX(ios): Add missing FlutterFramework dependency to SPM plugin configuration to resolve compilation failures and PlatformExceptions.

3.0.0 #

🎉 Major Features & Improvements #

  • FEAT: Massive Architecture Rewrite — Core algorithms (Location Filtering, Kalman Filter, Trip Management, Battery Budgeting, Schedule Parsing, Delta Encoding, Audit Trail) are now powered by a high-performance Rust Core using flutter_rust_bridge and UniFFI. This brings identically deterministic behavior and extreme battery efficiency across Android, iOS, and Dart.
  • FEAT: Smart Motion Mode — Introduced MotionDetectionMode.smart. This intelligent hybrid detection mode optimizes battery consumption dynamically by delegating evaluation to the Rust battery budget engine.
  • FEAT: Event bridge overhaul — Migrated all platform event channels to use strongly-typed Pigeon bridges, eliminating JSON serialization overhead completely.
  • FEAT: New Ecosystem Adapters — Introduced the official tracelet_supabase (Supabase Postgres background syncing & Auth) and tracelet_firebase (Firebase RTDB live location broadcasting) plugins.

2.1.0 #

🎉 Major Features & Improvements #

  • FEAT: Smart Foreground Notification Visibility (Android) — Added dynamic foreground service notification management. The notification now intelligently hides itself when the app is in the foreground, and reappears seamlessly when the app enters the background. This significantly reduces notification clutter while maintaining OS-level compliance.
  • FEAT: Speed-Based Motion Detection Mode — Introduced a new motion detection mode (tl.MotionDetectionMode.speed). In this mode, motion state transitions are driven directly by GPS speed calculations rather than raw accelerometer hardware. This provides enhanced compatibility and reliability on devices with aggressive sensor sleep policies, particularly in vehicular tracking scenarios.
  • FEAT: Strongly-Typed Enums Across Bridge — Fully refactored string-based config comparisons to typed enum indices across the Flutter, Pigeon, Android, and iOS layers. This eliminates magic strings and ensures type-safety across the entire plugin bridge.

🐛 Bug Fixes #

  • FIX (Core): Fixed Accelerometer "Deaf Period" During Stop Countdown — Fixed a critical flaw in both Android and iOS native SDKs where the accelerometer was completely shut down during the stopTimeout countdown. Previously, if the device was still for 5 seconds on a smooth road, it would begin the 60-second stop countdown and ignore any subsequent bumps or shakes. Now, the accelerometer remains active during the countdown and will correctly abort the stationary transition if motion resumes (#85).
  • FIX (iOS): Resolved Native Permission Prompt Loop — Fixed an issue where reinstalling the app on iOS would bypass the native "Change to Always Allow" permission dialog and incorrectly redirect users to the iOS Settings app. TraceletHasRequestedAlways is now properly reset upon notDetermined OS state.
  • FIX (Core): Corrected Exponential Retry Backoff Scaling — Fixed a critical unit discrepancy between Dart and Swift for retryBackoffCap and retryBackoffBase. Time values are now properly cast as milliseconds instead of seconds, resolving a severe bug where HTTP retries fired every 60ms during network failure, causing excessive CPU/network thrashing and a massive 58KB+ log flood.
  • FIX (Core): Resolved Location Stream Dropping Events — Refactored the core Tracelet.locationStream pipeline. Replaced the faulty asyncMap batch processing with a highly robust .expand() implementation. The Tracelet.locationStream now correctly parses, type-casts, and guarantees delivery of every individual Location object without throwing type '_Map<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>' or silently discarding valid coordinates.
  • FIX (Android): Prevent LocationEngine.stop from unintentionally overriding the global stateManager.enabled flag during speed-based motion transitions.
  • FIX (Example): Updated the example app's initialization config to enforce MotionDetectionMode.accelerometer as the default to ensure immediate indoor responsiveness upon installation.

2.0.8 #

  • FIX(ios): Resolved type casting bug for 64-bit Pigeon Int64 integer values across all iOS config mappings. Integer configurations (such as stopTimeout, locationUpdateInterval, etc.) are now correctly applied on iOS instead of silently falling back to defaults.
  • PERF(ios): Added optimization to skip the GPS distanceFilter override to continuous tracking during stopTimeout when preventSuspend is enabled. This significantly reduces stationary battery drain when using the audio keep-alive feature.
  • CHORE: Update platform-specific dependency constraints to ^2.0.8.

2.0.7 #

  • FIX: Corrected intToAuthStatus permission index mappings on Android and iOS — getLocationAuthorization() and requestLocationAuthorization() now return the correct AuthorizationStatus values (#80).
  • FIX: Resolved Android SQLCipher migration crashes by loading the SQLCipher native library explicitly before migration and decoupling classpath availability checks to avoid class loading errors (#78).
  • FIX: Prevented false positive shake events on Android by applying absolute values to motion sensor magnitude readings and fixed zero-timeout logic to immediately transition to stationary state when delay is zero or negative (#79).
  • FIX: Removed manual Kotlin Gradle Plugin (KGP) configuration to support Flutter's new Built-in Kotlin feature, resolving build warnings and failures on newer Flutter versions (#81).
  • CHORE: Update platform-specific dependency constraints to ^2.0.7.

2.0.6 #

  • PERF: Hardware-level sensor batching on Android reduces CPU wake-ups by over 90% during active accelerometer monitoring.
  • FIX: iOS BatteryBudgetEngine adjustments (distance filter, desired accuracy, periodic interval) are now correctly applied to the location engine.
  • PERF: iOS Heartbeat deduplication avoids redundant SQLite writes and HTTP sync attempts when stationary.
  • FIX: Restored fast stationary detection (~5s dwell window) on iOS by correcting sample calculations to match 10Hz accelerometer rate.
  • FEAT: Added graceful hardware fallback on Android to use TYPE_SIGNIFICANT_MOTION when the primary accelerometer is missing.
  • FEAT: Added explicit permission checks and events upon start when location permissions are missing.
  • CHORE: Update platform-specific dependency constraints to ^2.0.6.

2.0.5 #

  • FEAT: Added Tracelet.isHeadlessRegistered static getter. Returns true after registerHeadlessTask() has been called. Useful for diagnostic tools like tracelet_doctor to detect missing headless handlers.
  • CHORE: New companion package tracelet_doctor (v1.0.0) — drop-in diagnostic overlay widget. See tracelet_doctor.

2.0.4 #

  • FEAT: Integrated Kalman Location Filter GPS smoothing into the Flutter plugin and the dynamic config settings in the Example App.
  • CHORE: Update platform-specific dependency constraints to ^2.0.4.

2.0.3 #

  • FIX: Removed unreliable timestamp drift heuristic from Android and iOS location spoofing detection. This prevents valid locations from being incorrectly rejected when a device's wall-clock time is slightly out of sync with GPS UTC time.

2.0.1 #

  • FIX: Fixed persistent blue location indicator on iOS by properly conditionally disabling CLBackgroundActivitySession and continuous GPS in low-accuracy geofence-only mode.
  • CHORE: Bumped native SDK dependencies to 2.0.1.

2.0.0 #

🎉 Major Milestone: Tracelet 2.0.0 #

Tracelet 2.0.0 introduces a modernized configuration schema, robust type-safe platform communication via Pigeon, and a flexible dependency model to optimize app size and compatibility.

🚨 Breaking Changes #

  • Refactored Configuration: The Config model is now a nested compound structure. Fields are grouped into GeoConfig, AppConfig, AndroidConfig, HttpConfig, LoggerConfig, MotionConfig, GeofenceConfig, and SecurityConfig.
  • Android On-Demand Dependencies: Optional features (GMS Location, SQLCipher, Play Integrity) are no longer bundled by default, reducing APK size by ~16 MB. Developers must now explicitly add these to their android/app/build.gradle if required.
  • Pigeon Migration: All platform-to-native communication now uses strictly-typed Pigeon interfaces, improving reliability and eliminating magic string/map errors.
  • Removed Deprecated APIs: Permission methods that returned raw integers (e.g., getPermissionStatus, requestPermission) have been removed in favor of the strongly-typed Future<AuthorizationStatus> methods introduced in 1.9.0.

🛠️ Improvements #

  • Motion Sensitivity Tuning: Added shakeThreshold, stillThreshold, and stillSampleCount to MotionConfig, providing granular control over accelerometer-based motion detection across all platforms.
  • iOS Stability: Resolved a critical issue where native permission dialogs failed to appear by enforcing main-thread execution for all CoreLocation and CoreMotion requests.
  • Cross-Platform Parity: Aligned authorization status mapping across Android and iOS to ensure consistent behavior when checking permissions.
  • AOSP Support: Improved fallback to standard LocationManager on Android when Google Play Services are unavailable.

1.9.3 #

2: 3: - CHORE: Bump native SDK dependencies to 1.1.4. 4: - CHORE: Aligned repository podspec files and updated release documentation. 5: 6: ## 1.9.2

  • REFACTOR: Migrated TlTrackingMode to a strongly-typed enum across the entire Pigeon bridge. This improves type safety and developer experience by eliminating magic integers in the platform communication layer. Android and iOS native implementations now use the generated enum types directly.
  • FIX: Resolved "Unable to establish connection" regression in locationStream when secondary engines (like overlays or background isolates) detach. Ensured destroyAll() is correctly integrated into the primaryInstance guard on both Android and iOS to prevent resource leaks and duplicate registrations during hot restarts.
  • FIX: Tracelet.locationStream no longer goes silent when flutter_overlay_window (or any plugin using FlutterEngineGroup) creates a secondary in-process FlutterEngine. The primary-instance guard introduced in 1.9.0 (#51) blocked EventDispatcher re-binding for in-process overlay engines, causing Pigeon FlutterApi onLocation channel to report "Unable to establish connection". A Looper-based discriminator now distinguishes overlay engines (main-thread attach → re-bind dispatcher) from headless background engines (off-thread attach → full skip, preserving #51).
  • FIX: Android destroyAll() now guards all background-critical subsystems when stopOnTerminate: false (#65). httpSyncManager.stop(), scheduleManager.stop(), and stopHeartbeat() were still called unconditionally on every swipe-to-dismiss, permanently killing HTTP sync and heartbeat monitoring until the app was manually reopened. Fixed in native tracelet-sdk 1.1.2.
  • CHORE: Aligned PigeonTracelet serialization logic to use enum indices for backward compatibility with the high-level State model while maintaining type-safe internal bridge contracts.

1.9.1 #

  • FIX: Android destroyAll() now respects stopOnTerminate: false for continuous and geofence tracking modes (#63). locationEngine.destroy() was unconditionally called in onDetachedFromEngine(), racing with LocationService.onTaskRemoved() which bootstraps native tracking. Background location tracking now survives app swipe from recents when stopOnTerminate: false is configured.

1.9.0 #

  • FEAT: Strongly typed permission APIs (#57). Added getLocationAuthorization, requestLocationAuthorization, getNotificationAuthorization, requestNotificationAuthorization, getMotionAuthorization, requestMotionAuthorization, and requestTemporaryFullAccuracyAuthorization, all returning Future<AuthorizationStatus> instead of magic ints. The matching int-returning methods (getPermissionStatus, requestPermission, getNotificationPermissionStatus, requestNotificationPermission, getMotionPermissionStatus, requestMotionPermission, requestTemporaryFullAccuracy) are now @Deprecated and will be removed in 2.0.0.
  • FIX: Android LocationService no longer crashes the host app with RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground() (#59). Reproducible on real devices when using periodicUseForegroundService: true. Root cause: onStartCommand only promoted to foreground for ACTION_START, but the system can deliver intents for other actions (and null-intent sticky restarts after a system kill) under the same foreground-service contract. Fixed in native tracelet-sdk 1.1.0 by always promoting at the top of onStartCommand.
  • FIX: Geofence(extras: {...}) now correctly persists and is returned by getGeofences() and delivered in onGeofence events (#58). Bug was in tracelet_platform_interface's _mapToGeofence, which silently dropped extras and vertices when constructing the Pigeon payload; the 1.8.12 native fix had no effect because the data never crossed the platform channel. Affected both Android and iOS.
  • TEST: Added on-device integration test (example/integration_test/geofence_extras_test.dart) that round-trips addGeofencegetGeofences() to prevent regression.

1.8.13 #

  • PERF: Reduce first-fix latency on stationary → moving transitions on both iOS and Android. The native engines now fire an additional one-shot location request when motion starts, delivering a fresh GPS fix in ~1–5s instead of waiting for distanceFilter (iOS) or locationUpdateInterval (Android) on the continuous stream (#54).
  • FIX: Android — after a manual Tracelet.changePace(false) (force stationary), the SDK can now detect real motion and resume tracking automatically. Previously the wake-up sensors stayed torn down, leaving the SDK in a dead-state. iOS was unaffected.
  • FIX: Bump iOS native SDK to 1.0.11 and Android native SDK to 1.0.12.

1.8.12 #

  • FIX: Geofence extras now arrive in GeofenceEvent.extras on Android (previously always empty). Location extras are also correctly included when reading back persisted locations (#51 follow-up).
  • FIX: Bump Android native SDK to 1.0.11.

1.8.11 #

  • FIX: Geofence callbacks no longer silently stop during continuous tracking when a secondary FlutterEngine (e.g. Firebase background messaging) registers the plugin (#51).

1.8.10 #

  • FIX: Killed-state tracking now works reliably — stopBootTracking() deferred from initialize() to ready() so boot-mode native tracking survives until the Dart side explicitly takes over (#50).
  • FIX: Bump native SDKs to 1.0.10.

1.8.9 #

  • FEAT: Add syncInterval to HttpConfig — flush locations on a fixed timer instead of per-insert, for fleet/logistics use cases (#50).
  • FEAT: Bump native SDKs to 1.0.9.

1.8.8 #

  • FIX: HTTP sync payload now consistent between iOS and Android — Android cursorToLocation() and all location map producers now use canonical is_moving (snake_case) and ISO 8601 timestamps, matching iOS format (#48).
  • FIX: Bump native SDKs to 1.0.8.

1.8.7 #

  • FIX: Enhance overall stability by including pending 1.8.6 patches.
  • FIX: Bump native SDKs to 1.0.7.

1.8.6 #

  • FIX: getCurrentPosition(samples: 1) now forces a fresh GPS fix instead of returning stale cached locations — uses requestLocationUpdates/startUpdatingLocation instead of getCurrentLocation/requestLocation which may return cached data without waking GPS hardware (#46).
  • FIX: HTTP sync headers callback (setHeadersCallback) no longer invoked per-batch — eliminates unnecessary MethodChannel round-trip latency on every sync request. Token refresh now handled exclusively via setTokenRefreshCallback on 401.
  • FIX: Headless FlutterEngine no longer overwrites foreground httpSyncManager callbacks — fixes 10-second timeout on requestFreshHeaders caused by MethodChannel messages routed to the wrong Dart isolate.
  • FIX: Bump native SDKs to 1.0.6.
  • FIX: Privacy zones, audit trail, and encryption APIs now work before ready() — only require initialize() (DB creation), not active tracking.
  • FIX: getPrivacyZones() no longer throws _Map<Object?, Object?> type cast error — fix Pigeon-generated lazy cast for nested map types.

1.8.5 #

  • FIX: getCurrentPosition() falls back to last known location when GPS returns no fix (e.g. emulator, GPS-off) — fixes LOCATION_UNAVAILABLE errors (#46).
  • FIX: Bump native SDKs to 1.0.5.

1.8.4 #

  • FIX: Add isReady guards to all Android SDK methods — prevents UninitializedPropertyAccessException when called before ready() (re-fixes #46).
  • FIX: Pin native SDK dependencies to exact versions — prevents auto-resolving to incompatible newer native SDK releases.

1.8.3 #

  • FIX: Prevent crash when getState(), setConfig(), or any other method is called before ready() on iOS — comprehensive isReady guards across all native SDK methods (re-fixes #46).

1.8.2 #

  • FIX: Prevent crash when stop() is called before ready() on iOS — returns NOT_READY error instead of accessing uninitialized properties.
  • FIX: Guard soundManager access on Android to prevent UninitializedPropertyAccessException during motion state changes or cleanup.
  • FIX: Use LocationManagerCompat.isLocationEnabled() on Android — fixes NoSuchMethodError crash on API 26/27 devices.
  • FIX: Enterprise optional dependencies (SQLCipher, Play Integrity, security-crypto) now gracefully degrade at runtime when not on the classpath — no more NoClassDefFoundError crashes.
  • REFACTOR: Refined ProGuard/R8 consumer rules — narrower keep rules, added -dontwarn for optional enterprise dependencies.
  • DOCS: Updated INSTALL-ANDROID.md and DATABASE-ENCRYPTION.md with enterprise dependency setup instructions.

1.8.1 #

  • FIX: iOS periodic mode no longer shows persistent location indicator in the status bar.

1.8.0 #

  • FIX: Align location map format contract across Android, iOS, and Dart layers — fixes 9 format mismatches.
  • FEAT: Add Tracelet.destroySyncedLocations() — deletes only synced locations, returns count deleted.
  • FEAT: Auto-purge synced locations from database after successful HTTP sync.
  • TEST: Add 25 Dart location map format tests.
  • DOCS: Add help/LOCATION-MAP-FORMAT.md canonical format contract reference.

1.7.1 #

  • FIX: ConfigManager null-merge — partial setConfig() no longer overwrites existing values with null defaults (fixes periodic mode HTTP sync failure).
  • FEAT: Add Tracelet.destroySyncedLocations() — deletes only synced locations, returns count deleted.
  • FEAT: Auto-purge synced locations from database after successful HTTP sync.

1.7.0 #

  • FEAT: Migrate all event subscriptions from EventChannels to Pigeon FlutterApi platform streams.
  • FEAT: Add Location.fromTl and LocationActivity.fromTl factory constructors for Pigeon type conversion.
  • FIX: Headless geofence events no longer silently dropped on Android task removal (#43).
  • REFACTOR: Extract native SDKs to standalone modules — Android (Maven Central) and iOS (CocoaPods/SPM).
  • DOCS: Add local development workflow documentation to CONTRIBUTING.md.

1.6.3-alpha.1 #

  • FEAT: Migrate all event subscriptions from EventChannels to Pigeon FlutterApi platform streams.
  • FEAT: Add Location.fromTl and LocationActivity.fromTl factory constructors for Pigeon type conversion.
  • REFACTOR: Remove _eventChannels, _eventStreams maps, and _getEventStream() helper.
  • CHORE: Update cross-package dependency constraints to ^1.6.3-alpha.1.

1.6.2 #

  • FIX: Update tracelet_web dependency to ^1.6.1 — fixes 5 missing HTTP Sync method stubs that caused UnimplementedError on web.

1.6.1 #

  • FEAT: Add 401-aware retry — native HTTP sync now detects 401 responses, invokes the headless headers callback (registerHeadlessHeadersCallback) to refresh authorization tokens, and retries the request once with updated dynamic headers. Works in both foreground and killed-state (headless) modes.

1.6.0 #

  • FEAT: Add SSL certificate pinning via HttpConfig.sslPinningCertificates and HttpConfig.sslPinningFingerprints.
  • FEAT: Add dynamic HTTP headers — setDynamicHeaders(), setHeadersCallback(), refreshHeaders(), and headless registerHeadlessHeadersCallback().
  • FEAT: Add route context — setRouteContext() / clearRouteContext() to attach metadata to every synced location.
  • FEAT: Add custom sync body builder — setSyncBodyBuilder() and headless registerHeadlessSyncBodyBuilder().
  • TEST: Add 19 Dart unit tests for RouteContext, SyncBodyContext, and HttpConfig SSL fields.
  • TEST: Add 6 MethodChannel mock tests for new platform methods.
  • DOCS: Update API.md, CONFIGURATION.md, HTTP-SYNC.md with new sync feature documentation.
  • CHORE: Update cross-package dependency constraints to ^1.6.0.

1.5.0 #

  • FEAT: Boot-mode HTTP auto-sync — locations sync to your server even when the app is killed or the device reboots (Android).
  • FIX: Test server now correctly reads latitude/longitude from nested coords object.
  • CHORE: Update cross-package dependency constraints to ^1.5.0.

1.4.6 #

  • FIX: Rename native PermissionManager to TraceletPermissionManager to avoid class name collision with permission_handler_apple (#32).
  • CHORE: Update cross-package dependency constraints to ^1.4.6.

1.4.5 #

  • TEST: Add integration tests for GPS-off fallback and reduced accuracy field serialization.
  • CHORE: Update cross-package dependency constraints to ^1.4.5.

1.4.4 #

  • FEAT: Add reducedAccuracy field to Locationtrue when iOS 14+ grants only approximate location authorization.
  • FEAT: Example app now shows [REDUCED] tag and gpsFallback=ON indicator for reduced/fallback location states.
  • TEST: Add 5 unit tests for reducedAccuracy field (default, parse, snake_case, round-trip, copyWithCoords).
  • CHORE: Update cross-package dependency constraints to ^1.4.4.

1.4.3 #

  • FEAT: Add locationSource field to Location — classifies each fix as gps, wifi, cell, network, or unknown.
  • FEAT: Add gpsFallback field to ProviderChangeEvent — signals when the engine auto-downgrades to Wi-Fi/cell positioning because GPS is disabled.
  • CHORE: Update cross-package dependency constraints to ^1.4.3.

1.4.2 #

  • FIX: Dead reckoning activation now reliably detects GPS hardware state instead of using accuracy-based heuristic.
  • FIX: Mock detection heuristic no longer produces false positives for Wi-Fi/cell locations when GPS is disabled.
  • CHORE: Update cross-package dependency constraints to ^1.4.2.

1.4.1 #

  • FEAT: Dead reckoning — full native IMU sensor fusion for GPS-denied environments (tunnels, parking structures, urban canyons). Activates automatically on GPS loss, deactivates on GPS recovery.
  • CHORE: Update cross-package dependency constraints to ^1.4.1.

1.4.0 #

  • FEAT: Encrypted SQLite — Tracelet.isDatabaseEncrypted() and Tracelet.encryptDatabase() for at-rest database encryption (SQLCipher on Android, Data Protection on iOS).
  • FEAT: Device attestation — Tracelet.getAttestationToken() returns a platform attestation token (Play Integrity on Android, App Attest on iOS).
  • FEAT: Remote config — automatic fetch of remote configuration with ETag caching and onRemoteConfig event stream.
  • FEAT: Dead reckoning — Tracelet.getDeadReckoningState() stub for future accelerometer/gyroscope-based position estimation.
  • FEAT: Carbon estimator — Tracelet.getCarbonReport() returns CO₂ emission estimates from tracked journeys.
  • FEAT: Add SecurityConfig and AttestationConfig to Config model for enterprise feature configuration.
  • CHORE: Update cross-package dependency constraints to ^1.4.0.

1.3.7 #

  • FIX: Android — fix ClassNotFoundException crash on app upgrade for BootReceiver and other manifest-declared components (fixes #31).
  • CHORE: Update tracelet_android dependency constraint to ^1.3.7.

1.3.6 #

  • FIX: SQLQuery.start and SQLQuery.end now correctly filter locations on all platforms (Android, iOS, Web).
  • FIX: Add offset field to SQLQuery to match native handler expectations.
  • FIX: getCount() now accepts optional SQLQuery for time-bounded counting.
  • PERF: DeltaEncoder.encode is 2.1x faster (cached DateTime parsing, precomputed rounding factors).
  • PERF: GeoUtils.haversine optimized — fewer trig calls, precomputed constants.
  • CHORE: Update cross-package dependency constraints to ^1.3.6.

1.3.5 #

  • FIX: iOS — fix Unable to find module dependency: 'TraceletCore' build error.
  • CHORE: Update cross-package dependency constraints to ^1.3.5.

1.3.4 #

  • CHORE: Update cross-package dependency constraints to ^1.3.3.

1.3.3 #

  • FIX: Android — bundle native core Kotlin source (com.tracelet.core.*) directly inside the plugin package, fixing "Unresolved reference" build errors when installed from pub.dev.
  • FIX: iOS — bundle TraceletCore Swift source directly inside the plugin package instead of depending on an unpublished local CocoaPod.
  • CHORE: Remove React Native support to simplify the monorepo.

1.3.2 #

  • PERF: Android — streaming JsonWriter replaces per-location JSONObject allocations in batch sync (A-L5).
  • PERF: iOS — C-level UUID generation replaces Foundation UUID() in LocationEngine and TraceletDatabase (I-M6).
  • PERF: Performance audit now 77/77 items resolved (100%).

1.3.1 #

  • FIX: Resolve extras key collision between HttpConfig and PersistenceConfig — serialization keys renamed to httpExtras and persistenceExtras to prevent native ConfigManager flat-merge from overwriting one with the other. Backward-compatible via fromMap fallback.
  • PERF: Add 22 new benchmarks covering DeltaEncoder, BatteryBudgetEngine, CarbonEstimator, PersistDecider, Config/State serialization.
  • PERF: iOS markSynced() now uses chunked prepared statements (500 UUIDs/chunk) to avoid SQLite variable limit and improve sync performance.

1.3.0 #

  • FIX: getState() always returned enabled: false on iOS — the iOS StateManager.toMap() flat-merged config keys into the state dictionary, overwriting enabled and isMoving with config defaults. Fixed by nesting config under a "config" key, matching Android behavior (#26).

1.2.0 #

Breaking Changes #

  • REFACTOR: ForegroundServiceConfig.notificationPriority changed from int to NotificationPriority enum. Replace raw integers (-2..2) with enum values (NotificationPriority.min, .low, .defaultPriority, .high, .max).
  • REFACTOR: AuditConfig.hashAlgorithm changed from String to HashAlgorithm enum. Replace 'SHA-256' with HashAlgorithm.sha256, 'SHA-512' with HashAlgorithm.sha512, etc.
  • REFACTOR: MotionConfig.triggerActivities changed from comma-separated String to Set<ActivityType>. Replace 'on_foot, in_vehicle' with {ActivityType.onFoot, ActivityType.inVehicle}.

Notes #

  • Native platform channel serialization is backward-compatible — no native code changes required. notificationPriority still serializes as int, hashAlgorithm as "SHA-256" string, and triggerActivities as comma-separated string.

1.1.0 #

New Features #

  • FEAT: Add ComplianceReport model and Tracelet.generateComplianceReport() API for GDPR Article 30 / CCPA data processing inventory reports. Auto-generates a structured snapshot of all location data collection metadata including: totalLocationsStored, totalLocationsSynced, data retention policy (maxDaysToPersist, maxRecordsToPersist), timestamp bounds of stored records (oldestRecord, newestRecord), database encryption status, active privacy zone count and identifiers, HTTP sync URL and auto-sync state, audit trail status with chain validation, permission states (location + motion), and tracking configuration flags (sparse updates, Kalman filter, delta compression, tracking mode). Supports toJson() for automated tooling integration and toMarkdown() for human-readable audit documents.
  • FEAT: Add BatteryBudgetEngine algorithm — a feedback control loop that automatically adjusts distanceFilter, desiredAccuracy, and periodic interval to maintain a configurable battery budget. Set batteryBudgetPerHour in GeoConfig (typical range: 1.0–5.0 %/hr) to enable. Subscribe to Tracelet.onBudgetAdjustment() for real-time adjustment events showing current drain vs. target and the new parameters being applied.
  • FEAT: Add CarbonEstimator — per-trip and cumulative CO₂ emission calculator using EU EEA 2024 mode-specific emission factors (gCO₂/km): car = 192, bus = 89, train = 41, walking/cycling = 0. Integrates with activity recognition to track distance per transport mode via Haversine. Returns TripCarbonSummary with totalCarbonGrams, totalDistanceMeters, carbonByMode, distanceByMode, and dominantMode. Tracks cumulative totals across trips.
  • FEAT: Add DeltaEncoder algorithm — batch location compression codec using delta encoding with 60–80% payload reduction. First location transmitted in full; subsequent positions as deltas with shortened field names and configurable coordinate precision (5 = ~1.1 m, 6 = ~0.11 m). Native implementations provided on Android (Kotlin) and iOS (Swift) for consistency.
  • FEAT: Add RTree<T> spatial index — O(log n) geofence proximity queries supporting 10,000+ geofences with sub-millisecond lookup. Provides queryCircle() and queryBBox() APIs with Haversine-verified results.

New Configuration Fields #

  • FEAT: GeoConfig.batteryBudgetPerHour (double, default 0.0) — target max battery drain (%/hr). When > 0, enables BatteryBudgetEngine which auto-adjusts accuracy, distance filter, and sample rate. Overrides manual settings.
  • FEAT: GeoConfig.enableSparseUpdates (bool, default false) — app-level deduplication that drops locations within sparseDistanceThreshold (default 50 m) of the last recorded position. Unlike distanceFilter (which controls platform GPS sampling), this filters at the persistence layer. sparseMaxIdleSeconds (default 300) forces periodic "still here" updates.
  • FEAT: GeoConfig.enableDeadReckoning (bool, default false) — inertial navigation using accelerometer + gyroscope + compass when GPS is lost for longer than deadReckoningActivationDelay seconds (default 10). Auto-stops after deadReckoningMaxDuration seconds (default 120) to prevent IMU drift accumulation.
  • FEAT: HttpConfig.enableDeltaCompression (bool, default false) — enable delta encoding for batch HTTP syncs. deltaCoordinatePrecision (default 6) controls coordinate precision.
  • FEAT: HttpConfig.disableAutoSyncOnCellular (bool, default false) — skip auto-sync on cellular networks, only sync on WiFi. Supported on Android, iOS, and Web (via Network Information API).
  • FEAT: GeoConfig.enableAdaptiveMode (bool, default false) — dynamic sampling based on activity type + battery level + charging state. Activity profiles: still → 500 m, walking → 50 m, driving → 10 m; battery scaling progressively widens filter below 50%/20%/10%.
  • FEAT: Periodic mode configuration: periodicLocationInterval (60–43200 sec), periodicDesiredAccuracy, periodicUseForegroundService (Android — sub-15-min intervals), periodicUseExactAlarms (Android — AlarmManager precision).

Bug Fixes #

  • FIX: generateComplianceReport() and getHealthCheck() no longer crash with type 'Map<Object?, Object?>' is not a subtype of type 'Map<String, Object?>' errors. Platform channel maps are now safely converted via Map<String, Object?>.from() instead of direct as casts. Also fixed nested config sub-map casts (config, geo, http, audit, persistence) using null-safe is Map checks.

Infrastructure #

  • CHORE: Migrate melos configuration from standalone melos.yaml to pubspec.yaml under the melos: key for melos 7.x compatibility. All 13 scripts (analyze, format, format:fix, test, test:dart, pigeon, clean, pub:get, build:example:android/ios/web, coverage, benchmark) now run via melos run <name>.
  • CHORE: Adopt Dart pub workspaces — root pubspec.yaml declares workspace: listing all 6 packages; each package declares resolution: workspace. Removed 5 pubspec_overrides.yaml files that are no longer needed.
  • CHORE: Upgrade melos dependency from ^6.0.0 to ^7.0.0.

1.0.2 #

  • FIX: (Android/iOS) Geofence registrations were unconditionally destroyed on app termination and reset, even when stopOnTerminate: false was configured with trackingMode=1. Geofences now survive process death and are properly re-registered (#23).

1.0.1 #

  • FIX: HTTP auto-sync was not triggered during automatic location tracking on any platform — locations accumulated in the database without being synced to the server (#21).
  • FIX: (iOS) HttpMethod.put was silently ignored due to type mismatch in native config parsing.
  • FIX: (iOS) HTTP headers could be dropped when platform channel delivered mixed-type maps.
  • FIX: (iOS) maxBatchSize default corrected from 100 to 250.

1.0.0 #

🎉 Stable Release #

  • FEAT: First stable release of tracelet — production-grade background geolocation for Flutter.
  • DOCS: Add Play Store background location declaration guide.
  • REFACTOR: Remove third-party company name references — use generic flutter_background_geolocation throughout.
  • REFACTOR: Rename migration guide to MIGRATION-FROM-FBG.md.
  • All APIs are finalized and production-ready.

0.12.0 #

Performance Audit — 74 of 77 issues resolved #

  • PERF: Cache AdaptiveSamplingEngine instance instead of re-creating per GPS fix (D-C1).
  • PERF: Add Location.copyWithCoords() to eliminate toMap()/fromMap() round-trip in Kalman filter hot path (D-C2).
  • PERF: Wire trip detection to processed location stream, eliminating duplicate Location.fromMap() (D-H1).
  • PERF: Fast-path _castToMap with type check — avoids map copy when already correct type (D-H2).
  • PERF: Replace .expand() with .where() in _filterLocation to avoid single-element list allocations (D-H4).
  • PERF: Cancel adaptive activity subscription in removeListeners() (D-H7).
  • PERF: Use .toList(growable: false) for addGeofences/addPrivacyZones result lists (D-M6).
  • PERF: Invalidate cached stream pipeline on setConfig() so it rebuilds with new settings (D-M8).
  • PERF: Use Map.from() instead of .map() with MapEntry for extras in Location.fromMap() (D-L4).
  • REFACTOR: Deduplicate LocationProcessor parameter list in setConfig() (D-L5).

0.11.5 #

  • FIX: [Android/iOS] Polygon geofence vertices are now correctly persisted to the native SQLite databases. Previously, vertex data was silently dropped during addGeofence(), causing polygon geofences to revert to circular after app restart.

0.11.4 #

  • FIX: [iOS] Revert over-aggressive permission guards — When In Use permission now works correctly for all tracking modes. Only the killed-state auto-resume (autoResumeTracking) requires Always authorization. iOS enforces permission at the OS level.

0.11.3 #

  • FIX: [Android] Enforce ACCESS_BACKGROUND_LOCATION check on all killed-state restart paths (boot receiver, task removal, periodic alarms/workers). "While In Use" permission no longer triggers background tracking.
  • FIX: [iOS] Enforce .authorizedAlways check on killed-state auto-resume and guard allowsBackgroundLocationUpdates. "When In Use" permission no longer triggers tracking from killed state.- FEAT: Add Tracelet.hasBackgroundPermission static getter — convenience check that returns true when location permission is AuthorizationStatus.always.

0.11.2 #

  • DOCS: Fix 22 unresolved dartdoc references ([Enterprise], [Config.*], [isValid], [brokenAtIndex], [brokenAtUuid]).
  • CHORE: Tighten all platform package constraints to ^0.11.2 (fixes pub downgrade score penalty).

0.11.1 #

  • FEAT: Add canScheduleExactAlarms() and openExactAlarmSettings() static methods for Android exact alarm permission management.
  • FIX: Bypass LocationProcessor distance/accuracy/speed filters for periodic location events — every timed fix is now delivered regardless of movement.
  • CHORE: Bump platform interface to 0.11.1.

0.11.0 #

  • FEAT: Tamper-proof audit trail — SHA-256 hash chain for location integrity verification.
  • FEAT: Privacy zones — exclude, degrade, or event-only actions for geographic privacy control.
  • FEAT: AuditConfig sub-config with enabled, hashAlgorithm, includeExtrasInHash options.
  • FEAT: PrivacyZoneConfig sub-config with enabled toggle.
  • FEAT: PrivacyZone model with identifier, latitude, longitude, radius, action, degradedAccuracyMeters.
  • FEAT: AuditProof model for hash chain verification results.
  • FEAT: CRUD API: addPrivacyZone(), addPrivacyZones(), removePrivacyZone(), removePrivacyZones(), getPrivacyZones().
  • FEAT: Audit API: getAuditTrail(), verifyAuditTrail(), getAuditProof().
  • DOCS: Added AUDIT-TRAIL.md and PRIVACY-ZONES.md guides.
  • CHORE: Bump all platform packages to ^0.11.0.

0.10.0 #

  • FEAT: Periodic mode — Tracelet.startPeriodic() for GPS-friendly interval tracking. GPS icon visible only ~5–10 seconds per fix instead of permanently.
  • FEAT: GeoConfig periodic options: periodicLocationInterval, periodicDesiredAccuracy, periodicUseForegroundService, periodicUseExactAlarms.
  • FEAT: Three Android scheduling strategies: WorkManager (default, battery-optimal), foreground service (reliable timing), and AlarmManager exact alarms (precise, no notification).
  • FEAT: Example app: periodic mode UI section with start/stop toggle, custom settings dialog, and map integration with distinct cyan markers.
  • DOCS: Updated API.md, CONFIGURATION.md, BACKGROUND-TRACKING.md, and INSTALL-ANDROID.md with periodic mode and exact alarms documentation.
  • CHORE: Bump all platform packages to ^0.10.0.

0.9.1 #

  • FIX: iOS HttpSyncManager optional UIBackgroundTaskIdentifier unwrap safety.

0.9.0 #

  • FEAT: Adaptive sampling engine — auto-adjusts distanceFilter based on detected activity type, battery level, and speed. Enable with GeoConfig(enableAdaptiveMode: true). See Adaptive Sampling Guide.
  • FEAT: Health check API — Tracelet.getHealth() returns a comprehensive diagnostic snapshot covering tracking state, permissions, battery, sensors, database, and geofence state with actionable HealthWarning enum. See Health Check Guide.
  • FEAT: HTTP sync retry metadata — HttpEvent now includes isRetry and retryCount fields.
  • FEAT: Configurable motion sensitivity — MotionConfig gains shakeThreshold, stillThreshold, and stillSampleCount for tuning accelerometer-based motion detection.
  • FEAT: HealthWarningDescription extension with .description getter for human-readable warning text.
  • CHORE: Bump all platform dependencies to ^0.9.0.

0.8.3 #

  • FEAT: Unlimited geofences via proximity-based auto-load/unload — only geofences within geofenceProximityRadius are registered with the OS (up to 100 on Android, 20 on iOS), sorted by distance. Enables monitoring thousands of geofences despite platform limits.
  • FEAT: geofencesChange event fires when geofences are activated/deactivated from proximity monitoring.
  • CHORE: Bump all platform dependencies to ^0.8.3.

0.8.2 #

  • DOCS: Improve README visuals with combined Android & iOS demo image.

0.8.1 #

  • PERF: iOS background hardening — all native operations (location persist, HTTP sync, headless engine boot, lifecycle transitions) now wrapped in beginBackgroundTask for safe background execution.
  • FEAT: iOS 17+ CLBackgroundActivitySession support — extends background runtime for location tracking.
  • FEAT: iOS 18+ CLServiceSession support — maintains authorization state during background execution.
  • FIX: iOS preventSuspend lifecycle gaps — audio keep-alive now correctly started/stopped in all tracking modes and transitions.
  • FIX: Web EventChannel bridge — all event streams (onLocation, onMotionChange, onHeartbeat, etc.) were broken on web due to events being consumed but never forwarded. Now works correctly.
  • CHORE: Bump tracelet_ios to ^0.8.1, tracelet_web to ^0.8.1.

0.8.0 #

  • FEAT: OEM compatibility — automatic mitigations for aggressive OEM power management (Huawei, Xiaomi, OnePlus, Samsung, Oppo, Vivo).
  • FEAT: Tracelet.getSettingsHealth() — device health API returning manufacturer, aggression rating, battery optimization status, and available OEM settings screens.
  • FEAT: Tracelet.openOemSettings(label) — open OEM-specific settings screens (autostart, battery saver, app launch) by label.
  • DOCS: Comprehensive OEM-COMPATIBILITY.md guide with per-manufacturer instructions.
  • DOCS: Update README with OEM compatibility feature and documentation link.
  • CHORE: Bump all platform dependencies to ^0.8.0.

0.7.1 #

  • DOCS: Add mock location detection feature to README with documentation links and feature description.
  • DOCS: Add Mock Detection guide to documentation table.
  • CHORE: Bump all platform dependencies to ^0.7.1.

0.7.0 #

  • FEAT: Mock location detection & prevention — detect and reject spoofed GPS locations across Android, iOS, and Web.
  • FEAT: Location.isMock field — boolean flag indicating if a location came from a mock provider.
  • FEAT: Location.mockHeuristics field — MockHeuristics metadata (satellite count, elapsed realtime drift, timestamp drift, platform flag).
  • FEAT: LocationFilter.rejectMockLocations config — block spoofed locations from reaching the app.
  • FEAT: LocationFilter.mockDetectionLevel config — MockDetectionLevel enum (disabled, basic, heuristic) for configurable detection depth.
  • FEAT: ProviderChangeEvent.mockLocationsDetected — real-time alert when mock locations are detected.
  • FEAT: Re-export MockDetectionLevel from tracelet.dart barrel file.
  • DOCS: Comprehensive MOCK-DETECTION.md guide.
  • DOCS: Updated CONFIGURATION.md with mock detection options.
  • CHORE: Bump all platform dependencies to ^0.7.0.

0.6.1 #

  • CHORE: Bump all platform dependencies to ^0.6.1.

0.6.0 #

  • FEAT: Integrate shared Dart LocationProcessor into onLocation stream — distance filtering, elasticity, accuracy filtering, and speed filtering now run in Dart for cross-platform consistency.
  • FEAT: Integrate shared Dart GeofenceEvaluator for high-accuracy proximity checks.
  • FIX: Fix broadcast stream bug — stateful LocationProcessor and KalmanLocationFilter were called once per listener per event, causing second subscriber to see distance=0 and filter all locations. Now uses cached .asBroadcastStream() so transformations run exactly once.
  • PERF: Native code no longer duplicates filtering logic — significantly reduces native code surface.
  • CHORE: Bump all platform dependencies to ^0.6.0.

0.5.5 #

  • FIX: iOS onSchedule event now sends full state map instead of partial data.
  • CHORE: Bump all platform dependencies to ^0.5.5.

0.5.4 #

  • FIX: Heartbeat events no longer return zero coordinates on Android and iOS.

0.5.3 #

  • CHORE: Bump all platform dependencies to ^0.5.3.

0.5.2 #

  • FEAT: disableMotionActivityUpdates now falls back to permission-free accelerometer-only motion detection instead of disabling all motion detection entirely.
  • DOCS: Expanded MotionConfig.disableMotionActivityUpdates documentation with fallback behavior, use cases, and battery notes.
  • DOCS: Updated getMotionPermissionStatus() / requestMotionPermission() docs to reflect accelerometer-only mode behavior.
  • DOCS: Added "Opting Out of Motion Permission" section to PERMISSIONS.md with comparison table.

0.5.1 #

  • DOCS: Update README with web platform in architecture and documentation tables.

0.5.0 #

  • FEAT: Add web platform support via tracelet_web package.
  • FEAT: Guard registerHeadlessTask() for web compatibility (kIsWeb early return).
  • DOCS: Add Web Support guide (help/WEB-SUPPORT.md) with full API compatibility matrix.
  • DOCS: Update README with web platform in architecture table and documentation links.
  • CHORE: Bump all platform dependencies to ^0.5.0.

0.4.0 #

  • FEAT: getMotionPermissionStatus() and requestMotionPermission() APIs for activity recognition permission.
  • FIX: Speed always zero in motionchange events — track lastEffectiveSpeed across location updates.
  • DOCS: Split README into focused help guides (Permissions, Background Tracking, API, Configuration).
  • DOCS: Add side-by-side Android/iOS demo recordings.
  • CHORE: Bump all platform dependencies to ^0.4.0.
  • CHORE: Format all Dart files.

0.3.0 #

  • FEAT: One-shot location — getCurrentPosition() now supports persist, samples, maximumAge, and extras parameters for enterprise single-time location requests.
  • FEAT: getLastKnownLocation() — returns the last cached location without triggering the GPS, or null if unavailable.
  • FEAT: ForegroundServiceConfig.enabled — disable foreground service/notification for lightweight one-shot requests.
  • BREAKING: Bump all platform dependencies to ^0.3.0.

0.2.5 #

  • Fix LICENSE file format for proper SPDX detection on pub.dev.
  • Bump tracelet_android dependency to ^0.2.3.
  • Bump tracelet_ios dependency to ^0.2.4.

0.2.4 #

  • Bump tracelet_android dependency to ^0.2.2 (fixes config not applied to foreground notification).
  • Bump tracelet_ios dependency to ^0.2.3 (fixes config values ignored on iOS).

0.2.3 #

  • Bump tracelet_ios dependency to ^0.2.2 (fixes iOS ConfigManager crash).

0.2.2 #

  • Fix dangling library doc comment lint in _helpers.dart.

0.2.1 #

  • Add tracelet_android and tracelet_ios as explicit dependencies to fix default plugin resolution warnings.

0.2.0 #

  • Add isMoving field to State model.
  • Fix Config.toMap() — use nested map structure to prevent extras key collision.
  • Fix watchPosition listener leak — subscriptions now tracked and cancelled.
  • Fix removeListeners() to cancel all Dart-side stream subscriptions.
  • Change LogLevel default from off to info.
  • Complete ==/hashCode on all sub-config classes.
  • Extract shared deserialization helpers to reduce code duplication.
  • Fix example (LogConfigLoggerConfig).

0.1.1 #

  • Fix pubspec description length for pub.dev scoring.
  • Add SPDX license: Apache-2.0 identifier.
  • Add example/main.dart for pub.dev documentation score.

0.1.0 #

  • Initial release.
  • Full background geolocation API with 38 public methods.
  • 14 real-time event streams (location, motion, geofence, HTTP, etc.).
  • Comprehensive config system: GeoConfig, AppConfig, HttpConfig, MotionConfig, GeofenceConfig, PersistenceConfig, LoggerConfig.
  • Elasticity-based distance filter scaling.
  • Location filtering and denoising.
  • Headless Dart execution for background events.
  • Scheduling with cron-like expressions.
  • removeListeners() for centralized cleanup.
47
likes
160
points
7.09k
downloads

Documentation

Documentation
API reference

Publisher

verified publisherikolvi.com

Weekly Downloads

Production-grade background geolocation for Flutter. Battery-conscious tracking, geofencing, SQLite persistence, HTTP sync, and headless execution for iOS & Android.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#geolocation #location #background #geofencing #tracking

Funding

Consider supporting this project:

github.com
www.buymeacoffee.com
thanks.dev
www.patreon.com

License

Apache-2.0 (license)

Dependencies

collection, flutter, meta, tracelet_android, tracelet_ios, tracelet_platform_interface, tracelet_web

More

Packages that depend on tracelet

Packages that implement tracelet