-
Notifications
You must be signed in to change notification settings - Fork 175
Description
Summary
ComposeUiClusterRenderer in maps-compose 8.2.0 crashes with an
IllegalStateException when the user navigates back quickly while the cluster
renderer is still processing markers in the background.
The crash was introduced by compose-ui 1.10.0 (shipped with
material3 1.5.0-alpha15), which added a strict requirement that every
AbstractComposeView must be composed inside a view tree that propagates
ViewTreeLifecycleOwner. ComposeUiClusterRenderer renders Compose
composables into off-screen ComposeViews to produce BitmapDescriptors for
Google Maps markers; these views are detached from the window and therefore
have no ViewTreeLifecycleOwner, causing the crash.
The problem did not occur with compose-ui 1.9.x
(shipped by the same Compose BOM via material3 1.5.0-alpha14).
Stack Trace
java.lang.IllegalStateException: Composed into the View which doesn't propagate ViewTreeLifecycleOwner!
androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:338)
androidx.compose.ui.platform.AbstractComposeView.onMeasure(ComposeView.android.kt:441)
android.view.View.measure(View.java:29816)
com.google.maps.android.compose.clustering.ComposeUiClusterRenderer.renderViewToBitmapDescriptor(ClusterRenderer.kt:213)
com.google.maps.android.compose.clustering.ComposeUiClusterRenderer.getDescriptorForCluster(ClusterRenderer.kt:184)
com.google.maps.android.clustering.view.DefaultClusterRenderer.onBeforeClusterRendered(DefaultClusterRenderer.java:911)
com.google.maps.android.compose.clustering.ComposeUiClusterRenderer.onBeforeClusterRendered(ClusterRenderer.kt:169)
com.google.maps.android.clustering.view.DefaultClusterRenderer$CreateMarkerTask.perform(DefaultClusterRenderer.java:1082)
com.google.maps.android.clustering.view.DefaultClusterRenderer$MarkerModifier.performNextTask(DefaultClusterRenderer.java:728)
com.google.maps.android.clustering.view.DefaultClusterRenderer$MarkerModifier.handleMessage(DefaultClusterRenderer.java:700)
android.os.Handler.dispatchMessage(Handler.java:110)
android.os.Looper.loopOnce(Looper.java:273)
android.os.Looper.loop(Looper.java:363)
android.app.ActivityThread.main(ActivityThread.java:10060)
java.lang.reflect.Method.invoke(Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:632)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
Steps to Reproduce
- Use
maps-compose 8.2.0with theClustering()composable. - Configure a
clusterContentorclusterItemContentlambda (custom Compose
UI for clusters/markers) — this activatesComposeUiClusterRenderer. - Set
compose-uito 1.10.0 (e.g., viamaterial3 1.5.0-alpha15or a
Compose BOM that includescompose-ui 1.10.0). - Navigate to a screen that displays a
GoogleMapwith many markers so that
clustering is still rendering in the background. - Immediately perform a back gesture (predictive back or regular back)
before the cluster renderer finishes processing all markers. - Observe the crash.
Expected Behavior
Navigating back while cluster rendering is in progress should not crash the
app. Either:
ComposeUiClusterRenderershould cancel in-flight rendering when it detects
the parent screen is no longer active, orComposeUiClusterRenderershould supply aViewTreeLifecycleOwnerto its
off-screenComposeViewbefore callingmeasure(), so that
AbstractComposeView.ensureCompositionCreatedsucceeds even on a detached
view.
Actual Behavior
The app crashes immediately with:
java.lang.IllegalStateException: Composed into the View which doesn't propagate ViewTreeLifecycleOwner!
The crash occurs on the main thread via DefaultClusterRenderer$MarkerModifier
which uses an Android Handler to continue rendering even after the hosting
Activity/Fragment has moved on.
Root Cause Analysis
ComposeUiClusterRenderer.renderViewToBitmapDescriptor (ClusterRenderer.kt:213)
creates a detached ComposeView, calls view.measure(...), which triggers
AbstractComposeView.onMeasure → ensureCompositionCreated.
Starting with compose-ui 1.10.0, ensureCompositionCreated validates that
ViewTreeLifecycleOwner.get(this) != null. A detached view has no lifecycle
owner in its tree, so the check fails.
With compose-ui 1.9.x (the version bundled in material3 1.5.0-alpha14) this
validation was absent or lenient, so the same code path worked fine.
The fix must be applied in maps-compose:
- Set a
ViewTreeLifecycleOwneron the off-screen view before measuring it
(e.g., usingViewTreeLifecycleOwner.set(view, ProcessLifecycleOwner.get())),
or - Interrupt the
MarkerModifierhandler when the map composable leaves the
composition.
Workaround
Pin material3 to 1.5.0-alpha14 (or any version that does not pull in
compose-ui 1.10.0):
# gradle/libs.versions.toml
compose-material3 = { module = "androidx.compose.material3:material3", version = "1.5.0-alpha14" }This overrides the Compose BOM and avoids the breaking compose-ui change
until maps-compose is fixed.
Environment
| Component | Version |
|---|---|
maps-compose |
8.2.0 |
maps-compose-utils |
8.2.0 |
android-maps-utils |
4.1.0 |
compose-ui (crashing) |
1.10.0 (via material3 1.5.0-alpha15) |
compose-ui (working) |
1.9.x (via material3 1.5.0-alpha14) |
Compose BOM |
2026.02.01 |
maps-compose clustering API |
Clustering() composable with custom clusterContent + clusterItemContent |
| Cluster algorithm | NonHierarchicalViewBasedAlgorithm |
Android minSdk |
29 |
compileSdk |
36 |
| Kotlin | 2.3.10 |
| AGP | 9.1.0 |
Additional Notes
- The crash only occurs when custom Compose content is provided for cluster
rendering (i.e.,clusterContent/clusterItemContentlambdas are set),
because that activatesComposeUiClusterRendererinstead of the default
icon-based renderer. - The crash is reliably reproducible on fast back navigation; it does not occur
if the user waits for cluster rendering to complete before navigating away. - This is a regression introduced by the
compose-ui 1.10.0breaking
change —maps-composeneeds to be updated to satisfy the new lifecycle
requirement.