Repository navigation
fix: read RNMBXLocationModule on first use, not when the package loads - #4388
Merged
Merged
Conversation
locationManager read the native location module, and created its event emitter, at module scope, so loading @rnmapbox/maps created the module. On iOS that runs RNMBXLocationModule.init on the main thread, and its AppleLocationProvider reads CLLocationManager.accuracyAuthorization, a synchronous call to locationd that can hang an app that only draws a map. The module and the emitter are now read on first use, and stop(), which runs on every background transition, skips a module that was never read.
Contributor
|
Thanks, the root cause checks out. I reproduced it on main (the location module is read during package import) and confirmed that with this PR it's only read on first use, including across background/foreground, on an iOS simulator. One small request: please trim or remove the comment above |
Contributor
|
Merged, thanks @kristojorg for the fix and the detailed investigation! I'll trim the code comment in a small follow-up PR, so no need to change anything here. |
1 of 4 tasks
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
src/modules/location/locationManager.tsreads the native location module at module scope:NativeModules.RNMBXLocationModuleon iOS, and on Android the spec module, whose default export callsTurboModuleRegistry.getEnforcing. It also creates theNativeEventEmitterthere. So loading@rnmapbox/mapscreates the location module, also in an app that only draws a map.On iOS the module has
requiresMainQueueSetup = true, so the first read creates it synchronously on the main thread. ItsAppleLocationProvider()property initializer readsCLLocationManager.accuracyAuthorization, which is a synchronous call tolocationd. Whenlocationdis slow, the app hangs at startup. In our app, Sentry reports App Hangs of 2–4 s with this main-thread stack:This PR reads the module when it is first used:
getMapboxLocationManager()reads the native module on first use. On Android it callsTurboModuleRegistry.getEnforcingitself and imports only theSpectype, so importing the spec file no longer creates the module.getLocationModuleEventEmitter()creates theNativeEventEmitteron first use. It replaces theLocationModuleEventEmitterconstant, which the module exported but the package did not; only the tests used it.stop()runs on every background transition. It now calls the nativestop()only if the module was already read.RNMBXLocationModule(iOS and Android); the firstgetLastKnownLocation()does. Four of the six new tests fail onmain.On iOS the module is still created on the main thread, now on first use, so its
CLLocationManagerkeeps the main run loop.Not changed: the Mapbox Maps SDK makes the same
accuracyAuthorizationread inMapView.init(LocationDataModel.createDefault()→AppleLocationProvider.init), so every map view still makes it. That is reported at mapbox/mapbox-maps-ios#2447.Note on iOS: until JS uses
locationManager(directly or throughUserLocation),RNMBXLocationModule.sharedisnil.RNMBXCameraandRNMBXCustomLocationProvideralready check fornil, and the map then uses its own location provider. Camera follow withLocationPuckworks with the module never created (tested below). Not tested: aCustomLocationProviderthat is installed before JS first useslocationManager. In that case the module starts with anAppleLocationProvider, not the custom provider.Before and after
Reproducer below, Debug build of
/example, React Native 0.84.1 (bridgeless), MapboxMaps 11.23.1, iPhone 17 Pro simulator on iOS 26.5, Xcode 27.1. A temporaryNSLoginRNMBXLocationModule.initand a temporary swizzle of-[CLLocationManager accuracyAuthorization]log each read with its thread and stack (second block below, not part of this PR).On a simulator
locationdusually answers in a few ms, so the hang does not reproduce on its own. To show its effect, the swizzle can add 3 s to each read (REPRO_SLOW_LOCATIOND=1), as a stand-in for a slowlocationd. Once, on the first launch after installing a new build, the module'sAppleLocationProvider.inittook 29 s with no added delay; I could not make that happen again (fresh boot, reinstall, clean install).mainRNMBXLocationModule.init@rnmapbox/mapsloads, before the screen's own code runsaccuracyAuthorizationreads before the map loadsRNMBXLocationModule, 2 fromMapView)MapView)onDidFinishLoadingMaplocationManager.getLastKnownLocation()The main-thread stack of the first read on
main:With this PR the same stack appears only after "Use location" is pressed.
Also checked on the fixed build:
followUserLocationwithLocationPuck, nolocationManager): the camera follows the simulated location, andRNMBXLocationModuleis never created.mainthe reproducer createsRNMBXLocationModulewhen the package loads; with this PR, only when "Use location" is pressed. The full example app does not create it at launch. User Location Updates: locations arrive and continue after the app goes to the background and back.getLastKnownLocation()did not resolve on this emulator, onmainor with this PR.Checklist
CONTRIBUTING.mdyarn generatein the root folder (no changes)/exampleapp./example)The example app runs Mapbox v11 with the bridgeless New Architecture only, so the two iOS items are one configuration, and so are the two Android items.
yarn lint,yarn type:checkandyarn unittestpass.Component to reproduce the issue you're fixing
Register it as the root component in
example/index.js:AppRegistry.registerComponent(appConfig.expo.name, () => BugReportExample). It does not touchlocationManageruntil "Use location" is pressed.Reproducer (BugReportExample.js)
Temporary instrumentation (not part of this PR)
ios/RNMBX/RNMBXLocationModule.swift, first line ofinit():example/ios/RNMapboxExample/AppDelegate.mm(#import <CoreLocation/CoreLocation.h>and#import <objc/runtime.h>), called first inapplication:didFinishLaunchingWithOptions:. For 3 s per read:SIMCTL_CHILD_REPRO_SLOW_LOCATIOND=1 xcrun simctl launch <udid> org.reactjs.native.example.RNMapboxExample. Logs:xcrun simctl spawn <udid> log stream --predicate 'eventMessage CONTAINS "[repro]"'.android/src/main/java/com/rnmapbox/rnmbx/modules/RNMBXLocationModule.kt, first in the class body: