Skip to content

chore(geofence): remove on-device location-send and add monitoring-distance cap#748

Open
mrehan27 wants to merge 1 commit into
feature/geofence-on-devicefrom
geofence-remove-location-send
Open

chore(geofence): remove on-device location-send and add monitoring-distance cap#748
mrehan27 wants to merge 1 commit into
feature/geofence-on-devicefrom
geofence-remove-location-send

Conversation

@mrehan27

@mrehan27 mrehan27 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

MBL-1843

Why

The on-device sync used to send a coarsened device location to the backend. Removing it entirely means the SDK has no code path that transmits device location, and lets the backend own "return the right set." Replaces #742 (closed; scope changed from gating fetch-all to removing location-send).

⚠️ Held until backend ships return-all-without-location. Today /geofences/nearby requires lat/long; this PR's no-location fetch depends on that change landing first.

What

  1. Remove on-device location-send. fetchGeofences() sends no location; dropped the 2-arg overload and GeofenceCoordinateCoarsener. GeofenceSyncMode stays as a single-value (FETCH_ALL) documented seam so re-introducing a location mode is a localized change + deliberate release (prior impl in git history).
  2. maxMonitoringDistance cap (wire max_monitoring_distance). Caps how far a business geofence can be from the device to be registered. Semantics: null → 1000 km default (don't register another continent's geofence for a local user), 0 → disabled, below the trigger radius → fall back (avoids a dead-zone).
  3. Three-way empty fix. Distinguishes "no geofences at all" (register nothing) from "geofences exist but all beyond the cap" (register the movement trigger only, so an EXIT re-ranks them in as the device approaches). Without this the cap is self-defeating.
  4. Config self-correction at the wire→domain boundary: non-positive values fall back, positive out-of-range values clamp (local radius 100 m–5 km, remote-fetch expiry 1 min–7 d, dedupe 1 min–24 h). geofences remains the only required wire field (missing → parse fails → preserve cache + retry, fail-safe).

Testing

  • :geofence unit suite green (incl. new cap, three-way-empty, and coercion cases).
  • apiCheck + ktlint green via pre-commit.

Note

High Risk
Changes core geofence sync semantics and privacy (no location on wire) and depends on backend supporting location-less /geofences/nearby; mis-timed release could break sync or leave stale registrations.

Overview
Stops sending device location on geofence sync: fetchGeofences() is location-less (no lat/long query params), and GeofenceSyncMode.FETCH_ALL ensures movement no longer escalates to a remote re-fetch based on distance from the last API anchor.

refresh() now picks REMOTE / LOCAL / SKIP via a decision table: time staleness still forces fetch; ranking staleness is measured from the last movement-trigger registration (so a missed EXIT while the app was dead triggers local re-rank, not a network call when the cache is time-fresh). handleMovement() re-ranks locally except when there is no API anchor yet.

Adds server max_monitoring_distance (with clamp/fallback semantics) and applies it in GeofenceDistanceFilter. When geofences exist but none are within the cap, the SDK still registers only the movement trigger so EXIT can pull regions in as the user approaches—distinct from a truly empty geofence list.

Wire→domain config mapping now clamps radii and expiry windows; default fallbacks for local/remote radii are updated. Tests cover fetch-all behavior, cap filtering, three-way empty handling, and config coercion.

Reviewed by Cursor Bugbot for commit f6c15c0. Bugbot is set up for automated code reviews on this repo. Configure here.

…stance cap

Geofence sync no longer transmits device location. The backend returns the full
(capped) set and the SDK sends no location to fetch; remote fetch is time/staleness-
based and a movement trigger only re-ranks the cached set on-device.

The location-based "nearby" fetch path (and its coordinate coarsening) is removed
entirely rather than gated, so the SDK has no code path that transmits device
location. GeofenceSyncMode is kept as a single-value enum so a location-based mode
can be re-introduced deliberately later (backend support + a deliberate SDK release).

Adds maxMonitoringDistance: candidates farther than this from the current location
aren't registered with the OS; local re-rank re-adds them as the device approaches.
Defaults to a finite cap (the server omits the field today) so a user far from a
geofence doesn't register, say, another continent's; the server sends 0 to disable
the cap. Only meaningful with fetch-all, where the backend returns geofences
regardless of distance.

When geofences exist but all fall outside the distance, the movement trigger is still
registered so an EXIT can re-rank and re-register them as the device approaches. Only
a truly empty set (no geofences, or maxBusinessGeofences == 0) registers nothing, so
customers without geofences still pay zero runtime cost.

Hardens config parsing to coerce server values into sane bounds: non-positive values
fall back, positive out-of-range radii/expiries clamp, and a maxMonitoringDistance
below the trigger radius (a dead-zone) falls back to the default.

MBL-1843

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mrehan27 mrehan27 requested a review from a team as a code owner June 19, 2026 12:48
@mrehan27 mrehan27 self-assigned this Jun 19, 2026
@github-actions

Copy link
Copy Markdown

Sample app builds 📱

Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request.


@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f6c15c0. Configure here.

val maxBusinessGeofences: Int,
// Cap (m) on how far a business geofence can be from the device to be registered.
@SerialName("maxMonitoringDistance")
val maxMonitoringDistance: Float

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cached config breaks on upgrade

Medium Severity

Adding required maxMonitoringDistance without a serialization default makes existing persisted cached_config JSON fail to decode on SDK upgrade. The store wipes that key and treats config as missing, so server-tuned thresholds (including the monitoring cap) revert to SDK fallbacks until a new config is fetched.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f6c15c0. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not applicable — :geofence is unreleased, so no shipped SDK has a persisted cached_config. When it releases, maxMonitoringDistance ships with every other config field. Keeping it required for consistency.

@codecov

codecov Bot commented Jun 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.97872% with 16 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (feature/geofence-on-device@c85718c). Learn more about missing BASE report.

Files with missing lines Patch % Lines
.../kotlin/io/customer/geofence/GeofenceRepository.kt 79.24% 2 Missing and 9 partials ⚠️
...in/io/customer/geofence/api/GeofenceApiResponse.kt 93.10% 1 Missing and 1 partial ⚠️
.../kotlin/io/customer/geofence/di/DIGraphGeofence.kt 0.00% 2 Missing ⚠️
...lin/io/customer/geofence/api/GeofenceApiService.kt 50.00% 1 Missing ⚠️
Additional details and impacted files
@@                      Coverage Diff                      @@
##             feature/geofence-on-device     #748   +/-   ##
=============================================================
  Coverage                              ?   67.02%           
  Complexity                            ?     1069           
=============================================================
  Files                                 ?      203           
  Lines                                 ?     6204           
  Branches                              ?      859           
=============================================================
  Hits                                  ?     4158           
  Misses                                ?     1740           
  Partials                              ?      306           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown
  • java_layout: geofence-remove-location-send (1781873352)

@github-actions

Copy link
Copy Markdown
  • kotlin_compose: geofence-remove-location-send (1781873358)

@github-actions

Copy link
Copy Markdown

📏 SDK Binary Size Comparison Report

Module Last Recorded Size Current Size Change in Size
core 43.20 KB 43.20 KB ✅ No Change
datapipelines 43.82 KB 43.82 KB ✅ No Change
messagingpush 31.65 KB 31.65 KB ✅ No Change
messaginginapp 121.84 KB 121.84 KB ✅ No Change
tracking-migration 22.89 KB 22.89 KB ✅ No Change
location 16.46 KB 16.46 KB ✅ No Change
geofence 60.80 KB 64.11 KB ⬆️ +3.31KB

@github-actions

Copy link
Copy Markdown

Build available to test
Version: geofence-remove-location-send-SNAPSHOT
Repository: https://central.sonatype.com/repository/maven-snapshots/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant