fix(map): eliminate cluster-renderer FATAL and harden black-map paths#5715
Merged
Conversation
The maps-compose cluster renderer was our #1 FATAL (932 users on the live 2.7.14/29321010 build): passing `clusterItemContent` makes the library rasterize the chip through an off-screen ComposeView with no reachable ViewTreeLifecycleOwner, fired from an async Handler that the #5704/#5708 owner-propagation band-aids could not win against. Replace the Composable cluster path with a custom DefaultClusterRenderer that paints node chips as Canvas BitmapDescriptors (mirroring the #5702 marker migration), so no ComposeView is ever created and the crash class is eliminated rather than raced. Precision circles are preserved by exposing the renderer's unclustered-item set (the library's clusterItemDecoration hook is internal-gated and won't fire for a custom renderer). Trade-off: cluster chips are now static, matching the existing track/traceroute chips. Also address the "all black Google Maps" reports: - MapType.NONE fallback: only blank the Google base map when a custom tile provider actually builds; if it fails (bad {x}/{y}/{z} template, missing MBTiles), fall back to the selected base map instead of rendering NONE (solid black with no recourse). Log a warning on fallback. - Centralize Maps SDK init in a google-flavor MapsSdkInitializer: keep the synchronous single-arg initialize (preserves #5709's BitmapDescriptorFactory readiness) and register the renderer callback once to log which renderer (LATEST/LEGACY) actually loaded via Kermit. LEGACY is decommissioned, so we can only observe, not force — this turns "black for some users" reports into something correlatable in Crashlytics/Datadog. All changes are google-flavor only (fdroid uses OSMDroid). Both flavors compile; spotlessCheck and detekt pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jamesarich
added a commit
that referenced
this pull request
Jun 3, 2026
…ine-map crash structurally The late-May/early-June crash-fix flurry introduced app-side MapsInitializer calls from Compose to work around a "BitmapDescriptorFactory is not initialized" crash on the node-detail inline map (#5709), then extended it to the main map (#5715). maps-compose initializes the SDK itself when a GoogleMap creates its MapView, and the idiom is to build BitmapDescriptors inside a live map. Initializing from Compose (ahead of / racing the MapView's own init) correlates with the LATEST-renderer vector base map rendering black on affected devices ("Normal" black while satellite/terrain/hybrid render fine). Open-tag bisection of a field report (fine at open.16/17, black at open.20) places the inline-map regression at open.19 and the main-map regression at open.20. Revert to the pre-flurry, library-idiomatic initialization: - delete MapsSdkInitializer; remove all app-side MapsInitializer calls (the MapView init LaunchedEffect and MarkerBitmapRenderer's ensureMapsInitialized; buildNodeChipDescriptor drops its now-unused Context param) - fix the inline-map crash structurally: build the marker descriptor inside InlineMap's GoogleMap content, where the SDK is already initialized, exactly like the main map (which never crashed) - drop the redundant play-services-maps version pin; maps-compose pulls 20.0.0 transitively via maps-ktx Every descriptor build site now sits inside a live map, so no app-side init is needed and the crash cannot recur. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
What & why
1. Cluster-renderer crash — our #1 FATAL (932 users, live on
2.7.14 (29321010))com.google.maps.android.compose.clustering.ComposeUiClusterRenderer.renderViewToBitmapDescriptor—IllegalStateException: Composed into the View which doesn't propagate ViewTreeLifecycleOwner!Passing
clusterItemContentmakes maps-compose rasterize the node chip through an off-screenComposeViewthat has no reachableViewTreeLifecycleOwner/SavedStateRegistryOwner, fired from the cluster renderer's async Handler — a race the owner-propagation band-aids (#5704/#5708) could never win.Fix: replace the Composable cluster path with a custom
DefaultClusterRenderer<NodeClusterItem>that paints chips as CanvasBitmapDescriptors (the same approach #5702 used for plain markers). NoComposeViewis ever created, so the crash class is eliminated, not raced. Precision circles are preserved by exposing the renderer's unclustered-item set (the library'sclusterItemDecorationhook isinternal-gated and won't fire for a non-library renderer, so we draw theCircles directly).2. "All black Google Maps" — app-side fallback
MapViewsetMapType.NONEwhenever a custom tile provider URL was selected. IfgetTileProvider()returnednull(bad{x}/{y}/{z}template, missing local MBTiles), theTileOverlaywas skipped and the base map stayedNONE→ solid black, no recourse.Fix: resolve the provider once (hoisted/
remembered) and useMapType.NONEonly when a working provider exists; otherwise fall back to the user's selected base map, with aLogger.wso a broken source is diagnosable.3. Centralized Maps init + renderer logging (diagnostics)
New google-flavor
MapsSdkInitializer: keeps the synchronous single-argMapsInitializer.initialize(preserves #5709'sBitmapDescriptorFactoryreadiness guarantee) and registers the renderer callback once to log which renderer (LATEST/LEGACY) actually loaded, via Kermit (→ Crashlytics/Datadog through the existing sanctioned writers). The LEGACY renderer was decommissioned in March 2025 — we can only observe the renderer, not force it — so this turns the documented "Latest-renderer goes black for some users" reports into something correlatable in field telemetry.Reviewer notes
:androidApp:compileGoogleDebugKotlinand:androidApp:compileFdroidDebugKotlinpass, as dospotlessCheckanddetekt.NONEunderneath — needs server-side tile validation, a separate effort.assembleDebug/test/allTestsbaseline and on-device verification (cluster markers render + no crash; broken custom tile → base map; logcat renderer/fallback lines). CI will run the full suite.🤖 Generated with Claude Code