Skip to content

fix(map): render cluster markers in-scope to kill ClusterRenderer FATAL#5723

Merged
jamesarich merged 1 commit into
mainfrom
claude/admiring-pare-09c3ec
Jun 3, 2026
Merged

fix(map): render cluster markers in-scope to kill ClusterRenderer FATAL#5723
jamesarich merged 1 commit into
mainfrom
claude/admiring-pare-09c3ec

Conversation

@jamesarich

Copy link
Copy Markdown
Collaborator

What

Fixes the top Crashlytics FATAL on the map (the maps-compose cluster renderer crash) without losing the marker info-window popups and tap interactions.

Two parts:

  1. Reverts the shipped Canvas-marker + ViewTree-owner band-aid series (fix(map): replace MarkerComposable with Canvas-rendered bitmaps #5702fix(map): revert app-side Maps SDK init to library-idiomatic, fix inline-map crash #5719, back to before fix: address top Crashlytics crashes and non-fatals for build 29320984 #5684). That series had eliminated the crash by hand-drawing markers on a Canvas, but in doing so it dropped the native info-window popups and click interactions and never actually closed the cluster crash (it kept mutating into ViewTreeLifecycleOwner! / ViewTreeSavedStateRegistryOwner! variants). This restores MapView, WaypointMarkers, InlineMap to their pre-fix: address top Crashlytics crashes and non-fatals for build 29320984 #5684 state, deletes MarkerBitmapRenderer.kt, and re-pins play-services-maps to 20.0.0.

  2. Fixes the actual crash at its source, in one file (NodeClusterMarkers.kt).

Why / root cause

Verified against the maps-compose 8.3.0 + android-maps-utils 4.1.1 source:

  • MarkerComposable already bakes its icon via the in-scope rememberComposeBitmapDescriptor (parented to the live, attached host view that has valid ViewTreeLifecycleOwner/SavedStateRegistryOwner), and info windows render with the live marker compositionContext. So InlineMap / NodeTrack / Traceroute were never the crash and are left untouched.
  • Only Clustering(clusterItemContent = …) crashes: its ComposeUiClusterRenderer composes each item in a detached ComposeView that carries only a fake lifecycle owner and no SavedStateRegistryOwner. In the Navigation 3 / popup hierarchies that ComposeView can't resolve those owners → FATAL.

How

NodeClusterMarkers now bakes each chip in the maps compose scope with rememberComposeBitmapDescriptor(node) { PulsingNodeChip(...) } (the real Compose chip, not a Canvas reimplementation) and hands the finished BitmapDescriptors to a small custom DefaultClusterRenderer:

  • Icons are assigned in onBeforeClusterItemRendered / onClusterItemUpdated, which run on the cluster's background thread but are read-only — they look up a pre-baked descriptor and never compose, so the crash class is removed (not raced).
  • Native info windows are preserved (super sets title/snippet from NodeClusterItem) along with onClusterItemInfoWindowClick → navigate to node details and onClusterClick.
  • Precision circles are preserved by exposing the renderer's own unclusteredItems state (the library's clusterItemDecoration can't fire for a non-internal renderer, since ClusterRendererItemState is internal).

Reviewer notes

  • Scope is deliberately minimal: the only behavioral code change is NodeClusterMarkers.kt; the other map files are pure reverts to a known-good state.
  • Trade-off: marker icons for visible nodes are baked up front (the library baked lazily for unclustered items). Keyed on node, so a bitmap is only re-rendered when that node changes. Fine for typical meshes.
  • PulsingNodeChip's pulse is a one-shot animation; clustering already rendered it as a static bitmap, so nothing visual is lost by baking it.

Verification

  • ./gradlew :androidApp:compileGoogleDebugKotlin
  • ./gradlew spotlessCheck detekt
  • ./gradlew :androidApp:assembleGoogleDebug
  • ⚠️ Not yet device-verified — the real proof is clusters rendering chips + info-window popups with no FATAL in Crashlytics.

🤖 Generated with Claude Code

Revert the shipped Canvas-marker + ViewTree-owner band-aid series
(#5702-#5719, back to before #5684), which had lost the marker info-window
popups and tap interactions, then fix the actual crash at its source.

Root cause: only Clustering(clusterItemContent=...) crashes. maps-compose's
ComposeUiClusterRenderer builds a detached ComposeView carrying a fake
lifecycle owner and no SavedStateRegistryOwner -- the top Crashlytics FATAL
in Navigation 3 / popup hierarchies. MarkerComposable already bakes its icon
via the safe in-scope rememberComposeBitmapDescriptor, and info windows
render with the live marker compositionContext, so InlineMap / NodeTrack /
Traceroute were never affected and are left unchanged.

NodeClusterMarkers now bakes each chip in-scope via rememberComposeBitmapDescriptor
and hands the finished bitmaps to a custom DefaultClusterRenderer (icons assigned
on the cluster's background thread, read-only -- it never composes). Native info
windows (title/snippet) + onClusterItemInfoWindowClick -> node details and the
precision circles are all preserved.

Re-pins play-services-maps to 20.0.0 and removes MarkerBitmapRenderer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the bugfix PR tag label Jun 3, 2026
@jamesarich jamesarich merged commit e3e0945 into main Jun 3, 2026
17 checks passed
@jamesarich jamesarich deleted the claude/admiring-pare-09c3ec branch June 3, 2026 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant