Skip to content

Android: Crash when minZoomLevel and maxZoomLevel props change simultaneously #570

@christian-apollo

Description

@christian-apollo

Bug

MapViewController.setMinZoomLevel and setMaxZoomLevel throw IllegalArgumentException when React Native's Fabric renderer delivers the two prop updates in an unfavorable order.

Crash

Fatal Exception: java.lang.IllegalArgumentException: Minimum zoom level cannot be greater than maximum zoom level
       at com.google.android.react.navsdk.MapViewController.setMinZoomLevel(MapViewController.java:908)
       at com.google.android.react.navsdk.NavViewManager.setMinZoomLevel(NavViewManager.java:489)
       at com.facebook.react.viewmanagers.NavViewManagerDelegate.setProperty(NavViewManagerDelegate.java:122)
       at com.facebook.react.uimanager.ViewManager.updateProperties(ViewManager.java:102)
       at com.facebook.react.fabric.mounting.SurfaceMountingManager.updateProps(SurfaceMountingManager.java:810)

Steps to reproduce

  1. Render a NavigationView with both minZoomLevel and maxZoomLevel set to the same value (e.g. 16)
  2. Change both props simultaneously to a different value (e.g. 14)
  3. Fabric may apply maxZoomLevel=14 before minZoomLevel=14, creating a transient state where min(16) > max(14)
  4. App crashes

This is a race condition inherent to how Fabric applies individual props — the app has no control over the order.

Root cause

Both setMinZoomLevel and setMaxZoomLevel in MapViewController.java validate against the current stored preference of the other constraint and throw if the invariant is violated. Since Fabric can deliver prop updates in any order, there is no safe ordering a consumer can use when both values change in the same render.

Suggested fix

Instead of throwing, call mGoogleMap.resetMinMaxZoomPreference() before applying the new value, then re-apply the other stored preference. This makes each setter self-contained and order-independent:

@Override
public void setMinZoomLevel(float minZoomLevel) {
    if (mGoogleMap == null) return;

    minZoomLevelPreference = minZoomLevel;

    // Reset both preferences first so the new min/max pair is always
    // applied atomically. Without this, Fabric can deliver minZoomLevel
    // and maxZoomLevel prop updates in any order, causing a transient
    // state where min > max that crashes the app.
    mGoogleMap.resetMinMaxZoomPreference();

    float effectiveMin = (minZoomLevel < 0.0f) ? mGoogleMap.getMinZoomLevel() : minZoomLevel;
    mGoogleMap.setMinZoomPreference(effectiveMin);

    if (maxZoomLevelPreference != null) {
        float effectiveMax = (maxZoomLevelPreference < 0.0f) ? mGoogleMap.getMaxZoomLevel() : maxZoomLevelPreference;
        mGoogleMap.setMaxZoomPreference(effectiveMax);
    }
}

Same pattern for setMaxZoomLevel.

Environment

  • Library version: 0.14.2
  • React Native: 0.78 (Fabric / New Architecture)
  • Android

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions