[Android] Crash at onMapReady: applyBridgedProps -> moveToRegion -> newLatLngBounds throws "Map size can't be 0" apiexception (Pixel 9 Pro XL / Android 17)

Open
#5,978 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
58/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
android, java, react-native
Domain
mobile

Research direction

Start in MapView.java at moveToRegion, then inspect updateExtraData, animateToRegion, and the fitTo* methods for the same size-dependent camera calls. Verify the onMapReady path when the outer view is laid out but the internal map size is zero. Done means bounds operations no longer crash in that window and are deferred for reapplication with dimensions on a later layout.

Written by the indexing model from the issue text.

Description

Summary

Production crash reported by Google Play vitals: applying initialRegion at map-ready time crashes inside the Google Maps SDK with its obfuscated apiexception — the documented "Error using newLatLngBounds(LatLngBounds, int): Map size can't be 0. Most likely, layout has not yet occurred for the map view." error.

The crash path is onMapReady → applyBridgedProps() → moveToRegion() → map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0)) — i.e. MapView.java:534 → 826 → 881 in 1.27.2 (line numbers in the stack below match the released sources exactly).

All events so far come from one device: Pixel 9 Pro XL on Android 17 (API 37), repeating (9 events, same user), with the current Play-services-delivered maps renderer (policy_maps_core_dynamite@260830210). It looks like the newest OS/renderer combination has shifted the onMapReady-vs-internal-layout timing so the long-known race now fires consistently on that hardware. Expect more reports as Android 17 rolls out.

Stack trace
com.google.maps.api.android.lib6.common.apiexception.b:
  at com.google.maps.api.android.lib6.common.v.a (:com.google.android.gms.policy_maps_core_dynamite@260830210@260830203025.947725257.947725257:15)
  at com.google.maps.api.android.lib6.phoenix.camera.t.j (...:123)
  at com.google.maps.api.android.lib6.impl.ag.a (...:10)
  at com.google.maps.api.android.lib6.phoenix.camera.t.s (...:60)
  at com.google.maps.api.android.lib6.impl.cg.v (...:26)
  at com.google.android.gms.maps.internal.j.br (...:386)
  at m140.azc.onTransact (...:21)
  at android.os.Binder.transact (Binder.java:1230)
  at com.google.android.gms.internal.maps.zza.zzc (com.google.android.gms:play-services-maps@@19.1.0:2)
  at com.google.android.gms.maps.internal.zzg.moveCamera (com.google.android.gms:play-services-maps@@19.1.0:3)
  at com.google.android.gms.maps.GoogleMap.moveCamera (com.google.android.gms:play-services-maps@@19.1.0:2)
  at com.rnmaps.maps.MapView.moveToRegion (MapView.java:881)
  at com.rnmaps.maps.MapView.applyBridgedProps (MapView.java:826)
  at com.rnmaps.maps.MapView.onMapReady (MapView.java:534)
  at com.google.android.gms.maps.zzag.zzb (com.google.android.gms:play-services-maps@@19.1.0:1)
  at com.google.android.gms.maps.internal.zzas.zza (com.google.android.gms:play-services-maps@@19.1.0:5)
  at com.google.android.gms.internal.maps.zzb.onTransact (com.google.android.gms:play-services-maps@@19.1.0:3)
  at android.os.Binder.transact (Binder.java:1230)
  ...
  at android.os.Looper.loop (Looper.java:397)
  at android.app.ActivityThread.main (ActivityThread.java:9523)
Analysis

moveToRegion guards against the pre-layout case:

if (super.getHeight() <= 0 || super.getWidth() <= 0) {
    map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lng), 10));
    boundsToMove = bounds;
} else {
    map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0));   // <-- line 881, throws
    boundsToMove = null;
}

but the guard checks the outer RN view's size. The Maps SDK's camera keeps its own internal viewport size, which is registered asynchronously after the SDK attaches its internal view hierarchy (which itself happens asynchronously once the dynamite module loads). When onMapReady is delivered before that internal size registration completes, newLatLngBounds(LatLngBounds, int) — the only size-dependent CameraUpdate — throws, and since onMapReady runs on the main looper the app dies. So the crash fires exactly when the outer view is already laid out (guard passes, else-branch taken) but the internal size is still 0.

The window is normally microscopic, which is why this has been a rare-but-persistent crash across every Google Maps wrapper for years (same class as flutter/flutter#135874, and the ancient #245 / #3154 here). What's new is that on Android 17 with the current renderer it reproduces persistently on at least one mainstream device.

Two amplifying observations:

  • In our app the map most exposed is a 1×1, offscreen, alpha-0 MapView used as an engine warm-up. Its outer size (1×1) always passes the > 0 guard, so it runs the vulnerable branch on every mount, and a never-drawn view plausibly has a much wider internal-size-registration window. A tiny/offscreen <MapView initialRegion={...}/> on an Android 17 device may be a good reproduction setup.
  • animateToRegion (MapView.java:1395) calls newLatLngBounds(bounds, 0) with no size guard at all, so any animateToRegion issued around mount time has the same exposure.
Suggested fix

Mirror the intent of the existing safe branch: wrap the size-dependent call and fall back to the deferred path, which updateExtraData already knows how to re-apply with explicit dimensions (newLatLngBounds(bounds, width, height, 0)) on the next layout:

} else {
    try {
        map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0));
        boundsToMove = null;
    } catch (RuntimeException e) {
        // Internal map size not registered yet ("Map size can't be 0") — defer to
        // updateExtraData, which re-applies with explicit dimensions on next layout.
        map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lng), 10));
        boundsToMove = bounds;
    }
}

(catch (RuntimeException) because the concrete exception class is an obfuscated internal type of the dynamite module and can't be referenced.) The same treatment would fit animateToRegion and the fitTo* methods.

Happy to open a PR with the above if maintainers agree with the approach.

Workaround for other users

On Android, use the camera forms instead of the region forms — CameraUpdateFactory.newCameraPosition has no layout/size restriction:

  • initialCamera instead of initialRegion (on Fabric it is even applied via GoogleMapOptions at map construction, so no post-ready camera move happens at all);
  • animateCamera instead of animateToRegion for moves that can occur near mount.

Note when constructing camera objects for Android: always include pitch and headingcameraPositionFromMap reads them with ungated getDouble, so omitting them throws NoSuchKeyException.

Environment
  • react-native-maps: 1.27.2 (crash line numbers match this release exactly); the unguarded call is still present on current master
  • react-native: 0.86.0 (New Architecture / Fabric), Expo SDK 57
  • react: 19.2.3
  • play-services-maps: 19.1.0 (the library default)
  • Maps renderer (Play-services delivered): com.google.android.gms.policy_maps_core_dynamite@260830210
  • Device: Google Pixel 9 Pro XL, Android 17 (API 37) — 9 crash events, single device, repeating; no occurrences observed on older Android versions in the same app population
  • Map: default provider (Google), initialRegion set, no region/camera props
Dominant language
TypeScript
Stars
16k
Forks
5k
Avg merge
9d 9h
Merged PRs (30d)
3

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from react-native-maps/react-native-maps

All issues in react-native-maps/react-native-maps

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.