chore(geofence): acquire location without emitting analytics events#1140
Conversation
Sample app builds 📱Below you will find the list of the latest versions of the sample apps. |
SDK binary size reports 📊SDK binary size of this PRSDK binary size diff report vs. main branch |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## feature/geofence-on-device #1140 +/- ##
==============================================================
+ Coverage 71.61% 72.10% +0.48%
==============================================================
Files 247 248 +1
Lines 13072 13140 +68
==============================================================
+ Hits 9362 9475 +113
+ Misses 3710 3665 -45 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Let hosts use geofencing without emitting "CIO Location Update" analytics
events or enriching identify context. Adds an internal
requestLocationUpdateSilently() that acquires a fix, posts LocationAcquired,
and records it as last-known only — no track event, no persistence, not in
identify enrichment. It runs regardless of LocationConfig.mode (geofencing
needs a fix even when tracking is off) while still requiring permission.
Splits the location cache: getLastKnownLocation() reflects any fix (incl.
silent) in-memory, while the persisted cache that feeds enrichment stays
tracking-only. Adds GeofenceLocationMode {automatic (default), manual} and
CustomerIO.geofence.refreshFromCurrentLocation().
The geofence refresh decision runs at app launch (SDK/module init), on
identify, and on a fresh location fix, gated on an identified user; in
.automatic it self-acquires a silent fix when none is available. Refreshes
anchor at the last registration center (falling back to the location cache)
so a refresh after background movement doesn't re-rank from a stale fix, and
identify no longer falsely arms the first-run rearm when an anchor exists.
Sample app drives geofencing via refreshFromCurrentLocation() after a
background permission grant.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4a69388 to
f850c24
Compare
| /// Set by a host-initiated `refreshFromCurrentLocation()`. The next acquired fix drives a | ||
| /// refresh regardless of the no-location rearm flag, so a manual refresh works even when it | ||
| /// does not coincide with a first-run no-anchor skip. | ||
| private let explicitRefreshRequested = Synchronized<Bool>(false) |
There was a problem hiding this comment.
should this be cleared on logout?
There was a problem hiding this comment.
Good catch — added 9c5a3c6f. On ResetEvent both arming flags (explicitRefreshRequested and lastSkippedForNoLocation) are now cleared synchronously, so a previous user's pending refresh can't carry into the next session. Matches Android's onUserSignedOut.
On ResetEvent, clear explicitRefreshRequested and lastSkippedForNoLocation so a previous user's pending refresh intent can't drive a sync for the next user. Cleared synchronously in the observer so it lands before a re-login's identify re-arms. Matches Android's onUserSignedOut. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What & why
Lets hosts use on-device geofencing without emitting
CIO Location Updateanalytics events or enriching identify context with precise location. Geofencing
needs a fix even when location tracking is
.off; today acquisition and trackingare coupled, so a geofence-only customer would leak precise location.
Changes
Location module
@_spi(Geofence) requestLocationUpdateSilently(): acquires aone-shot fix, posts
LocationAcquiredEvent, records it as last-known only — noCIO Location Updatetrack, not persisted, not in identify enrichment. Runsregardless of
LocationConfig.mode; still requires location permission.lastKnownLocation(any source, incl.silent) backs
getLastKnownLocation(); the persisted cache (analytics/identifyGeofence module
GeofenceLocationMode { automatic (default), manual }onGeofenceModuleConfig..automaticself-acquires when geofencing needs a fix and none is available;.manualwaits for the host and never acquires location on its own.CustomerIO.geofence.refreshFromCurrentLocation()— the way to drivegeofencing in
.manual, and a forced refresh in.automatic. Arms an explicitrefresh, then requests a silent fix.
existing identify and location-acquired triggers.
Sample app (APN-UIKit): on permission grant, calls
CustomerIO.geofence.refreshFromCurrentLocation()instead ofrequestLocationUpdate().Behavior parity
Mirrors the Android implementation: silent primitive, cache split, mode enum,
facade, explicit-refresh arm, and the refresh decision on app launch + identify +
location-acquired.
Tests
cleared on reset; persisted cache untouched by silent fixes.
tracking is
.off, but emits no analytics.or track.
launch; auto-acquire at launch in
.automatic;.manualarms the refresh butnever self-acquires; identify triggers a refresh; explicit-refresh arm; no
false-arm when an anchor already exists.
Note
Medium Risk
Changes how location fixes flow into analytics, identify enrichment, and geofence refresh timing (including launch and user-reset), which affects privacy behavior and geofence correctness across sessions.
Overview
Decouples geofence-only location use from analytics and identify enrichment so apps can sync nearby geofences without emitting
CIO Location Updateor persisting precise coordinates for profile context.The Location module adds an internal
@_spi(Geofence)requestLocationUpdateSilently()path that still postsLocationAcquiredEventand updates in-memory last-known, but skips persisted cache, tracks, and identify enrichment whentrackis false.processLocationUpdate(_:track:)gates persistence and pipeline sync ontrack; publicgetLastKnownLocation()now reads the split storage (in-memory last-known with fallback to the tracking cache). Silent acquisition runs even whenLocationConfig.modeis.off.The Geofence module adds
GeofenceLocationMode(.automatic/.manual), publicCustomerIO.geofence.refreshFromCurrentLocation(), launch-time refresh alongside identify/location-acquired triggers, identified-user gating, explicit-refresh arming cleared on reset, and.automaticself-acquire via silent location when no anchor exists. The sample app callsrefreshFromCurrentLocation()on permission grant instead ofrequestLocationUpdate().Reviewed by Cursor Bugbot for commit 9c5a3c6. Bugbot is set up for automated code reviews on this repo. Configure here.