Skip to content

[Bug]: Android NullPointerException in NavViewModule when a controller method runs before the map is ready #645

Description

@christian-apollo

Is there an existing issue for this?

  • I have searched the existing issues

Description of the bug

On Android, NavViewModule.moveCamera (and every other controller-backed method in NavViewModule.java) crashes the app with a NullPointerException when the call reaches the fragment after it has been created but before its MapViewController exists.

MapViewFragment and NavViewFragment create mMapViewController inside the getMapAsync callback, so there is a window between fragment creation and map-ready in which getMapController() returns null. NavViewModule only null-checks the fragment:

IMapViewFragment fragment = mNavViewManager.getFragmentByNativeId(nativeID);
if (fragment == null) {
  promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
  return;
}
fragment.getMapController().moveCamera(cameraPosition.toHashMap()); // NPE when the map is not ready yet

Because the runnable is posted with UiThreadUtil.runOnUiThread, the exception is thrown on the main looper and takes the whole app down. NavViewManager already guards this case in getMapControllerProperties (fragment.getMapController() != null), so the module is the only place that dereferences the controller without a check. main still has the same code as 0.17.1.

Production stack trace (Crashlytics, Android, @googlemaps/react-native-navigation-sdk@0.17.1):

Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.google.android.react.navsdk.m.C(java.util.HashMap)' on a null object reference
       at com.google.android.react.navsdk.NavViewModule.lambda$moveCamera$9(NavViewModule.java:259)
       at android.os.Handler.handleCallback(Handler.java:1095)
       at android.os.Handler.dispatchMessageImpl(Handler.java:135)
       at android.os.Handler.dispatchMessage(Handler.java:125)
       at android.os.Looper.loopOnce(Looper.java:269)
       at android.os.Looper.loop(Looper.java:367)

Line 259 is the fragment.getMapController().moveCamera(...) call; the obfuscated m.C(HashMap) is MapViewController.moveCamera.

iOS Platform

  • iOS (not affected: messaging a nil controller is a no-op there)

Android Platform

  • Android

React Native version

0.86.3 (New Architecture)

React version

19.2.8

Package version

0.17.1

Native SDK versions

Android Navigation SDK 7.9.0 (from the package's android/build.gradle)

React Native Doctor Output

Not relevant to this report: the crash is a plain null dereference in the module source and reproduces on a stock emulator.

Steps to reproduce

  1. Mount a MapView or NavigationView.
  2. From JS, call any controller-backed NavViewModule method for that view (moveCamera, addMarker, setZoomLevel, setFollowingPerspective, ...) after the native view has mounted but before onMapReady fires. The controller returned by onMapViewControllerCreated is handed out at mount time, so this is easy to hit when an app remounts a view (for example swapping MapView and NavigationView) and reuses readiness state from the previous instance.
  3. If the call lands after the fragment transaction has committed and before the getMapAsync callback has run, the app crashes with the trace above.

To land a call in that window deliberately on an emulator, poll every 5 ms right after mounting the view with a fragment-only method and moveCamera back to back: setNavigationUIEnabled resolving (fragment exists) while moveCamera rejects or crashes shows the call arrived inside the window. In our runs the window was open at about 50 ms after mount on a Pixel-class emulator; it is wider on real devices under load, which is where the production crashes come from.

Expected vs Actual Behavior

Expected: the promise rejects with NO_MAP_ERROR_CODE, the same way it does when the fragment does not exist yet.

Actual: NullPointerException on the main thread, fatal for the app.

Code Sample

Minimal fix we are shipping as a patch, applied to every method in NavViewModule.java that dereferences getMapController() (19 methods):

-          if (fragment == null) {
+          if (fragment == null || fragment.getMapController() == null) {
             promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
             return;
           }

Verified on device: with the guard, a moveCamera that arrives while setNavigationUIEnabled already succeeds for the same view comes back as a NO_MAP_ERROR_CODE rejection and the app keeps running.

Additional Context

Happy to open a PR with the change above if that helps.

Activity

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

Metadata

Metadata

Assignees

Labels

priority: p0Highest priority. Critical issue. P0 implies highest priority.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions