Skip to content

feat(ios): native location watch relayed through JS - #29677

Merged
chrisnojima merged 7 commits into
nojima/HOTPOT-js-06-androidfrom
nojima/HOTPOT-js-07-livelocation
Sep 23, 2026
Merged

chrisnojima merged 7 commits into
nojima/HOTPOT-js-06-androidfrom
nojima/HOTPOT-js-07-livelocation

Conversation

@chrisnojima

Copy link
Copy Markdown
Contributor

Stack 5/7 on #29650.

Why

  • Shares can leave location running. The service drops an expired share without clearing its watch (test(chat/maps): failing tests for live location watch lifecycle #29668), and significant-change monitoring outlives the process. The OS keeps tracking and relaunching the app after sharing is over.
  • A stray clear breaks the next share. An unmatched chatClearWatch pushed the watch refcount below zero, so the next share never started the OS watch.
  • No distance throttle. Every fix the OS delivers in the background is posted, including jitter from a phone that isn't moving.

What this changes

  • react-native-kb (iOS): a new KbLocationWatcher wraps CLLocationManager. It enables background updates and significant-change monitoring, and exposes startLocationWatch/stopLocationWatch and an onLocationFix event, and the pod adds CoreLocation. The distance filter follows the app lifecycle from the first PR in this stack.
  • JS (iOS):
    • The refcounted chatWatchPosition/chatClearWatch drive the native watch, and the refcount can no longer go below zero.
    • Each fix goes through shouldRecordFix, then localLocationUpdate.
    • Init stops any watch a previous process left running, and a failing stop can't abort init.
    • Init also removes the expo task left by earlier builds, which expo would otherwise restore on every launch.
  • Android: unchanged; it still uses expo-location.

Judgment calls

  • The throttle lives in JS, not native. react-native-kb has no XCTest target, and every fix already crosses JS to reach Go.
    • In the background, a fix is recorded once it is at least both fixes' accuracies added together from the last recorded fix, with that distance clamped to 65–200 m.
    • A fix more than twice as sharp as the last recorded one is also recorded.
    • In the foreground every fix is recorded.
  • distanceFilter is None while the app is active and 65 m otherwise. That keeps master's every-fix behaviour on screen.
  • A share goes quiet after a background relaunch until it expires or the user shares again. The relaunch runs no JS, so no fix reaches Go. Go restores the share, but its watch request finds no chat UI. Then, when the app is opened, init stops the leftover OS monitoring.
  • No relaunch guard for significant-change launches (stopping on launchOptions[.location]). Without one, each background relaunch while a killed share lingers costs one Go init and a launch flush.
  • Native reports no app state for background fixes. Go's LocationUpdate already moves BACKGROUND to BACKGROUNDACTIVE.

Tests

  • location-throttle.test.ts ports the Go throttle table and its sequence tests: jitter, slow drift, outlier anchor, coarse anchor. It also adds a case for the half-accuracy clause.
  • location-watch.test.ts covers:
    • one native start and stop per share
    • foreground and background fixes
    • throttle reset
    • result before start
    • a failed start
    • the refcount floor
    • a single listener
    • init stopping a leftover watch, even when the stop throws

Mutation-checked.

iOS live location runs on a CLLocationManager in the react-native-kb module
instead of the expo-location background task. JS still answers the service's
chatWatchPosition/chatClearWatch, refcounts the native watch, and forwards
fixes with localLocationUpdate. Native only applies a 65 m distanceFilter and
emits every fix; JS throttles out of the foreground (a move must exceed both
fixes' accuracies summed, clamped to 65-200 m, or be a fix more than twice as
sharp), restarting the throttle with each watch. The throttle's jest table
ports the go/chat/maps throttle cases.

The legacy expo task is unregistered at startup so expo stops restoring a
second location manager. Android keeps the expo-location path.
…ix while active

Significant-change monitoring survives termination and keeps relaunching the
app, and the service drops an expired share without clearing its watch, so JS
init now stops the native watch before the engine can deliver a restored
chatWatchPosition. Native stop creates the manager if needed and always stops
both update streams when the watch is not wanted.

The native distance filter is off while the app is active (every fix, as the
expo path reported) and 65 m otherwise, following the lifecycle state native
already reports. The clear-watch refcount no longer goes negative, and a
restarted watch drops any fix listener a failed stop left behind.
@chrisnojima
chrisnojima added this pull request to stack #29680 September 23, 2026 19:54
@chrisnojima
chrisnojima removed this pull request from stack #29680 September 23, 2026 19:56
@chrisnojima
chrisnojima added this pull request to stack #29681 September 23, 2026 19:56
@chrisnojima
chrisnojima merged commit 305ee0e into master Sep 23, 2026
1 check passed
@chrisnojima
chrisnojima deleted the nojima/HOTPOT-js-07-livelocation branch September 23, 2026 20:53
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