|
| 1 | +diff --git a/node_modules/@rnmapbox/maps/android/src/main/mapbox-v11-compat/v11/com/rnmapbox/rnmbx/v11compat/Location.kt b/node_modules/@rnmapbox/maps/android/src/main/mapbox-v11-compat/v11/com/rnmapbox/rnmbx/v11compat/Location.kt |
| 2 | +index 8cda49e..87f4b2d 100644 |
| 3 | +--- a/node_modules/@rnmapbox/maps/android/src/main/mapbox-v11-compat/v11/com/rnmapbox/rnmbx/v11compat/Location.kt |
| 4 | ++++ b/node_modules/@rnmapbox/maps/android/src/main/mapbox-v11-compat/v11/com/rnmapbox/rnmbx/v11compat/Location.kt |
| 5 | +@@ -50,8 +50,15 @@ class LocationObserverAdapter(val callback: LocationEngineCallback): LocationObs |
| 6 | + callback.onSuccess(LocationEngineResult(locations.last())) |
| 7 | + } |
| 8 | + } |
| 9 | ++// Each registration remembers the provider the observer was actually registered on. |
| 10 | ++// `LocationEngine.locationProvider` is swapped whenever the request changes, so unregistering |
| 11 | ++// through the *current* provider would leave the observer attached to the previous one — a |
| 12 | ++// callback the caller already tore down, still being fed location updates. |
| 13 | ++// Plain class, not a data class: identity equality is what `contains`/`removeAll` want here. |
| 14 | ++class LocationObserverRegistration(val observer: LocationObserverAdapter, val provider: DeviceLocationProvider) |
| 15 | ++ |
| 16 | + class LocationEngine(var locationProvider: DeviceLocationProvider, var request: LocationProviderRequest) { |
| 17 | +- var observers: MutableList<LocationObserverAdapter> = mutableListOf() |
| 18 | ++ var observers: MutableList<LocationObserverRegistration> = mutableListOf() |
| 19 | + } |
| 20 | + |
| 21 | + |
| 22 | +@@ -73,17 +80,53 @@ fun LocationEngine.requestLocationUpdatesV11(callback: LocationEngineCallback, l |
| 23 | + } |
| 24 | + } |
| 25 | + val observer = LocationObserverAdapter(callback) |
| 26 | ++ // Publish into `observers` *before* registering with the provider, and pin the provider we |
| 27 | ++ // register on. A concurrent `removeLocationUpdates` (same two threads described below) that |
| 28 | ++ // lands between the two steps would otherwise never see this observer and would leave it |
| 29 | ++ // registered — feeding location updates to a callback the caller already tore down, on a |
| 30 | ++ // provider instance the request refresh above may have replaced. Registration itself stays |
| 31 | ++ // outside the monitor: no Mapbox SDK call is ever made while holding it. |
| 32 | ++ val provider = locationProvider |
| 33 | ++ val registration = LocationObserverRegistration(observer, provider) |
| 34 | ++ synchronized(observers) { |
| 35 | ++ observers.add(registration) |
| 36 | ++ } |
| 37 | + if (looper != null) { |
| 38 | +- locationProvider.addLocationObserver(observer, looper) |
| 39 | ++ provider.addLocationObserver(observer, looper) |
| 40 | + } else { |
| 41 | +- locationProvider.addLocationObserver(observer) |
| 42 | ++ provider.addLocationObserver(observer) |
| 43 | ++ } |
| 44 | ++ // Gone from the list means a removal ran while we were registering, and its |
| 45 | ++ // `removeLocationObserver` hit a provider that did not know this observer yet. Undo here. |
| 46 | ++ val canceled = synchronized(observers) { !observers.contains(registration) } |
| 47 | ++ if (canceled) { |
| 48 | ++ provider.removeLocationObserver(observer) |
| 49 | + } |
| 50 | +- observers.add(observer) |
| 51 | + } |
| 52 | + |
| 53 | ++// `observers` is reached from more than one thread: LocationManager.enable() runs on the |
| 54 | ++// main thread when the activity resumes, and again on the React native-modules thread when |
| 55 | ++// RNMBXLocationModule.start()/setMinDisplacement() land. Unsynchronized, the two overlap |
| 56 | ++// inside Kotlin's `removeAll { }` (filterInPlace), whose trailing `removeAt(readIndex)` |
| 57 | ++// walks indices captured before the other thread shrank the list — IndexOutOfBoundsException |
| 58 | ++// "Index 0 out of bounds for length 0", crashing the app on foreground. |
| 59 | ++// |
| 60 | ++// Guard both mutations, and match on identity via a plain collection so `filterInPlace` |
| 61 | ++// is not involved at all. `removeLocationObserver` runs outside the lock: it calls into the |
| 62 | ++// Mapbox SDK and must not be holding our monitor while it does — and it goes through each |
| 63 | ++// registration's own provider, not `locationProvider`, which a later request refresh may |
| 64 | ++// already have replaced. |
| 65 | ++// |
| 66 | ++// Dropping an observer from the list is also the cancel signal for a registration still |
| 67 | ++// in flight on the other thread — see `requestLocationUpdatesV11`, which re-checks |
| 68 | ++// membership after registering and unregisters itself if it was pulled out meanwhile. |
| 69 | + fun LocationEngine.removeLocationUpdates(callback: LocationEngineCallback) { |
| 70 | +- observers.filter { it.callback == callback }.forEach { locationProvider.removeLocationObserver(it) } |
| 71 | +- observers.removeAll { it.callback == callback } |
| 72 | ++ val stale = synchronized(observers) { |
| 73 | ++ val matched = observers.filter { it.observer.callback == callback } |
| 74 | ++ observers.removeAll(matched.toSet()) |
| 75 | ++ matched |
| 76 | ++ } |
| 77 | ++ stale.forEach { it.provider.removeLocationObserver(it.observer) } |
| 78 | + } |
| 79 | + |
| 80 | + fun LocationEngine.getLastLocation(callback: LocationEngineCallback) { |
0 commit comments