What’s New in Tracelet
Stay up-to-date with the latest features, bug fixes, and performance improvements across the Tracelet ecosystem.
tracelet
v3.8.93.8.9
FIX: (Android, iOS) trips are now recorded natively in every motion mode and from changePace(). The native trip manager — the one that stamps trip_id onto stored locations and driving events (#402) and stores trip summaries (#401) — was fed only from the sensor-driven motion handler. On Android, SMART mode left that handler early, SPEED-mode transitions never reached it, and changePace() bypassed it in every mode; on iOS the accelerometer path sent its motion change before the trip manager had seen the transition. In all of these, Dart's onTrip still fired, but under an id Dart minted itself: the stored rows carried trip_id: null and no summary was stored or synced. Every motion change the session emits now records the transition first and carries the native trip id (#442).
FEAT: (Android, iOS) completed-trip summaries are stored and synced. Since #402 every location and driving event has carried a trip_id, but the trip summary itself only existed as the onTrip() event — never stored, never sent, and lost if the Dart isolate was gone when the trip ended. Each completed trip is now stored once on the device, under its trip_id, and goes out with the next sync as a root-level trips array beside location and telematics, still one POST: trip_id, started_at, ended_at, distance (m), duration (s), average_speed (m/s), start_location, stop_location, synced. A trip is marked sent only when the request carrying it succeeds, so an offline device delivers it later; a trip that ends after its last location was uploaded goes out on its own request. Custom body builders receive the same objects as SyncBodyContext.trips (headless: event['trips']). Additive: existing payload keys are unchanged and trips is absent when there is nothing to send. Also: a flush carrying only telematics (no locations) now counts as delivered when the server accepts it; it used to be reported as a failure, so those events were sent again on every flush (#401).
FIX: (iOS) TraceletCore.xcframework and TraceletSyncFFI.xcframework now ship a dSYM for every slice. Archiving for TestFlight or the App Store ended with "Upload Symbols Failed: The archive did not include a dSYM for the TraceletCore.framework", and a crash inside the Rust core was unsymbolicated in Xcode Organizer and Crashlytics: the release build discarded its debug info and the packaging never produced a symbol file. The iOS build now keeps line tables, extracts them into a dSYM per slice, strips the shipped binary (so it is no larger), and attaches the dSYMs to the xcframeworks, from where Xcode carries them into the archive. If you added a post_install hook setting DEBUG_INFORMATION_FORMAT = dwarf for the Tracelet targets to silence the warning, remove it — it never affected the prebuilt frameworks and only dropped the Swift pods' dSYMs (#424).
FIX: (Android, iOS) auto-sync now drains the whole location queue instead of one batch per new fix. Each trigger uploaded a single batch and returned, and a fix stored while a sync was in flight scheduled nothing — the "already running" check covered the debounce and the request with all its retries. So the queue could only shrink by one batch per later fix, and with maxBatchSize: 1 it could not shrink at all: the server replayed the route one point per new fix, ended a 100 km trip halfway along it, and stayed stuck while the device was parked, since a parked device stores nothing. Every trigger now uploads batch after batch until the queue is empty or a batch fails, and a request made during a sync is picked up before it finishes. A failed batch ends the run without retrying in a loop, so a down server is not hammered. Also: autoSyncThreshold is honoured (it was stored but never read); the custom-body path uses httpTimeout, retryBackoffBase and retryBackoffCap instead of a fixed 15 s timeout and a fixed delay, and no longer retries a request that timed out after its body was sent, which delivered the batch twice; sync() and the syncInterval timer share the auto-sync lock, so the two cannot send the same rows, and a full batch hands the rest of the queue to the drain; and locationsOrderDirection: descending now orders rows inside the payload instead of selecting the newest rows, which had cleared older rows that were never sent. On iOS, sync() with maxBatchSize: 0 read zero rows and never uploaded; it now uses the 250-row default, as Android already did (#437).
FIX: (Android, iOS) prod crash-model licenses now unlock. A prod key always failed — 401 missing_integrity_token without a device token, 401 attestation_unconfigured with one — because it depended on an attestation step that could not complete for a customer's app, and iOS had none at all; shipped apps needed a hand-issued key. The license server now binds a prod license to the app it was issued for, and the SDK's unlock request reports the platform, the application id and, on Android, the SHA-256 of the signing cert, so a key used by a different app is refused. No integrity token or native setup is needed: paste the key from the portal into ImpactConfig as before. Keys from older SDK versions keep unlocking (#439).
FIX: (Android) stopping or parking tracking now actually stops the location request. Every time the SDK changed the live request in place — updateLocationProviderOptions(), a setConfig() while streaming, the GPS fallback and its restore, the battery-budget ladder — the Play-services client registered a new callback and forgot the old one, which stayed registered with nothing able to remove it. After the next stop() or changePace(false) that orphan kept delivering high-accuracy fixes every few seconds until the process died; the reporter's battery ran flat after 4 h 40 m of GNSS with no tracked request, and no public API could end it. Re-requesting now replaces the subscription, as the AOSP client already did, and a callback that is no longer current unregisters itself and drops the fix instead of recording it (#432).
FIX: (Android) reopening the app after a killed-state stationary period no longer drops tracking to periodic. When the killed-state engine parks a still device it records the stationary pace as PERIODIC, and ready() read that as the tracking strategy: it resumed startPeriodic(), which stopped the foreground service and the motion detector, so tracking stayed degraded — one fix a minute, or none — even while the user walked. The same misreading made a START_STICKY restart after process death decline itself as "periodic without a foreground service", and the boot path then built no engine and no motion detector, leaving a cached process with nothing to wake it that the low-memory killer reaped again and again — the importance=400 LOW_MEMORY exits in #436. The strategy the app started is now stored separately from the pace and is what ready(), setConfig(), task removal and a sticky restart resume; an explicitly periodic session still resumes periodic, and a restored session that was parked comes back parked with its detector armed (#428, #436).
FIX: (Android) crash/fall detection now gets accelerometer samples without ACTIVITY_RECOGNITION. start() started the motion detector only once that permission was granted, so a denied prompt — or a background start with no activity to ask from — left impact detection, gyroscope/barometer corroboration and motion detection off for the whole session, even with disableMotionActivityUpdates: true, the mode documented as needing no permissions; the killed-state paths had the same gate. Those sensors need no runtime permission, so the detector now always starts: only activity transitions need ACTIVITY_RECOGNITION, they are skipped (and the log says so) without it and registered if it is granted later, and accelerometer-only mode no longer asks for it at all (#433).
FIX: (iOS) continuous tracking no longer gets suspended in the background on iOS 16.4+ when the blue location bar is off. Apple suspends an app that runs both startUpdatingLocation() and significant-change monitoring if it sets a distance filter or coarse accuracy, unless the location indicator shows; the SDK always runs both and passed distanceFilter (default 10 m) straight to CoreLocation. Before 3.8.8 an unconditional CLBackgroundActivitySession showed the indicator and hid the rule, so the #423 fix exposed it: an Always-authorized app with showsBackgroundLocationIndicator: false and no session delivered nothing in the background except at significant-change wakes. In that configuration CoreLocation now runs with no distance filter and accuracy of 100 m or better; the configured distanceFilter still gates what is recorded and synced, so stored volume does not change. Either opt-in (showsBackgroundLocationIndicator or useBackgroundActivitySession), or When-In-Use authorization, leaves the configured values in place (#435).
tracelet_doctor
v3.8.93.8.9
Version alignment with tracelet 3.8.9.
tracelet_sync
v3.8.93.8.9
FEAT: (Android, iOS) the auto-sync drain attaches pending trip summaries to each request as a root trips array, sends a trips-only request when a trip ends after its last location was uploaded, and marks them sent only after the request succeeds (#401).
FIX: (iOS) the vendored TraceletSyncFFI.xcframework carries a dSYM per slice, so archives no longer warn "Upload Symbols Failed" for TraceletSyncFFI.framework (#424).
FIX: (Android, iOS) auto-sync drains the queue batch after batch instead of uploading one batch per inserted location, and a location stored while a sync is in flight is no longer dropped. TraceletSyncSink (Android) runs through the SDK's AutoSyncDrainer; the iOS SyncCoordinator keeps a pending flag and loops until a batch is empty or fails. Both honour autoSyncThreshold, read the oldest rows (the configured order applies inside the payload), share their lock with a manual sync(), and in the custom-body path use httpTimeout and retryBackoffBase/retryBackoffCap and do not retry a request that timed out after its body was sent (#437).
tracelet_supabase
v3.8.93.8.9
Version alignment with tracelet 3.8.9.
tracelet_firebase
v3.8.93.8.9
Version alignment with tracelet 3.8.9.
tracelet_android
v3.8.93.8.9
FIX: the pinned native SDK records trips in SMART and SPEED mode and from changePace(), so stored rows carry trip_id and trip summaries are stored; no Dart or plugin change (#442).
FEAT: custom sync body builders receive completed-trip summaries — trips in the foreground and headless builder arguments, from the pinned native SDK's getTripsForCustomBuilder() (#401).
FIX: the pinned native SDK's sync() shares the auto-sync lock and hands a full batch's remainder to the drain; no Dart or plugin change (#437).
FIX: the pinned native SDK reports the app id and signing cert on crash-model unlock, so prod licenses unlock; no Dart or plugin change (#439).
FIX: the pinned native SDK replaces the fused location subscription when it re-requests in place instead of orphaning the previous callback, so no request outlives stop() or changePace(false); no Dart or plugin change (#432).
FIX: the pinned native SDK resumes the strategy start() chose rather than the stationary pace the killed-state engine wrote, so ready() after a killed-state park keeps the foreground service and motion detector and a sticky restart is no longer declined; no Dart or plugin change (#428, #436).
FIX: the pinned native SDK starts the motion detector without ACTIVITY_RECOGNITION, so crash/fall detection gets accelerometer samples; no Dart or plugin change (#433).
tracelet_ios
v3.8.93.8.9
FIX: the pinned native SDK stamps the native trip id on the motion change changePace sends, so Dart adopts it instead of minting its own; no Dart or plugin change (#442).
FEAT: custom sync body builders receive completed-trip summaries — trips in the foreground and headless builder arguments, from the pinned native SDK's getTripsForCustomBuilder() (#401).
FIX: the vendored TraceletCore.xcframework carries a dSYM per slice, so archives no longer warn "Upload Symbols Failed" for TraceletCore.framework (#424).
FIX: the pinned native SDK's sync() shares the auto-sync lock, reads the oldest rows, and hands a full batch's remainder to the drain; no Dart or plugin change (#437).
FIX: the pinned native SDK reports the bundle id on crash-model unlock, so prod licenses unlock; no Dart or plugin change (#439).
FIX: the pinned native SDK runs CoreLocation unfiltered at 100 m accuracy or better when the background indicator is off and no session is open under Always, so iOS 16.4+ no longer suspends continuous tracking in the background; no Dart or plugin change (#435).