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
- Render a
NavigationView with both minZoomLevel and maxZoomLevel set to the same value (e.g. 16)
- Change both props simultaneously to a different value (e.g.
14)
- Fabric may apply
maxZoomLevel=14 before minZoomLevel=14, creating a transient state where min(16) > max(14)
- 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
Bug
MapViewController.setMinZoomLevelandsetMaxZoomLevelthrowIllegalArgumentExceptionwhen React Native's Fabric renderer delivers the two prop updates in an unfavorable order.Crash
Steps to reproduce
NavigationViewwith bothminZoomLevelandmaxZoomLevelset to the same value (e.g.16)14)maxZoomLevel=14beforeminZoomLevel=14, creating a transient state wheremin(16) > max(14)This is a race condition inherent to how Fabric applies individual props — the app has no control over the order.
Root cause
Both
setMinZoomLevelandsetMaxZoomLevelinMapViewController.javavalidate 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:Same pattern for
setMaxZoomLevel.Environment