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.0 with the Clustering() composable.
- Configure a
clusterContent or clusterItemContent lambda (custom Compose
UI for clusters/markers) — this activates ComposeUiClusterRenderer.
- Set
compose-ui to 1.10.0 (e.g., via material3 1.5.0-alpha15 or a
Compose BOM that includes compose-ui 1.10.0).
- Navigate to a screen that displays a
GoogleMap with 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:
ComposeUiClusterRenderer should cancel in-flight rendering when it detects
the parent screen is no longer active, or
ComposeUiClusterRenderer should supply a ViewTreeLifecycleOwner to its
off-screen ComposeView before calling measure(), so that
AbstractComposeView.ensureCompositionCreated succeeds 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
ViewTreeLifecycleOwner on the off-screen view before measuring it
(e.g., using ViewTreeLifecycleOwner.set(view, ProcessLifecycleOwner.get())),
or
- Interrupt the
MarkerModifier handler 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 / clusterItemContent lambdas are set),
because that activates ComposeUiClusterRenderer instead 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.0 breaking
change — maps-compose needs to be updated to satisfy the new lifecycle
requirement.
Summary
ComposeUiClusterRendererinmaps-compose 8.2.0crashes with anIllegalStateExceptionwhen the user navigates back quickly while the clusterrenderer is still processing markers in the background.
The crash was introduced by
compose-ui 1.10.0(shipped withmaterial3 1.5.0-alpha15), which added a strict requirement that everyAbstractComposeViewmust be composed inside a view tree that propagatesViewTreeLifecycleOwner.ComposeUiClusterRendererrenders Composecomposables into off-screen
ComposeViews to produceBitmapDescriptors forGoogle 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
Steps to Reproduce
maps-compose 8.2.0with theClustering()composable.clusterContentorclusterItemContentlambda (custom ComposeUI for clusters/markers) — this activates
ComposeUiClusterRenderer.compose-uito 1.10.0 (e.g., viamaterial3 1.5.0-alpha15or aCompose BOM that includes
compose-ui 1.10.0).GoogleMapwith many markers so thatclustering is still rendering in the background.
before the cluster renderer finishes processing all markers.
Expected Behavior
Navigating back while cluster rendering is in progress should not crash the
app. Either:
ComposeUiClusterRenderershould cancel in-flight rendering when it detectsthe parent screen is no longer active, or
ComposeUiClusterRenderershould supply aViewTreeLifecycleOwnerto itsoff-screen
ComposeViewbefore callingmeasure(), so thatAbstractComposeView.ensureCompositionCreatedsucceeds even on a detachedview.
Actual Behavior
The app crashes immediately with:
The crash occurs on the main thread via
DefaultClusterRenderer$MarkerModifierwhich uses an Android
Handlerto continue rendering even after the hostingActivity/Fragment has moved on.
Root Cause Analysis
ComposeUiClusterRenderer.renderViewToBitmapDescriptor(ClusterRenderer.kt:213)creates a detached
ComposeView, callsview.measure(...), which triggersAbstractComposeView.onMeasure→ensureCompositionCreated.Starting with
compose-ui 1.10.0,ensureCompositionCreatedvalidates thatViewTreeLifecycleOwner.get(this) != null. A detached view has no lifecycleowner in its tree, so the check fails.
With
compose-ui 1.9.x(the version bundled inmaterial3 1.5.0-alpha14) thisvalidation was absent or lenient, so the same code path worked fine.
The fix must be applied in
maps-compose:ViewTreeLifecycleOwneron the off-screen view before measuring it(e.g., using
ViewTreeLifecycleOwner.set(view, ProcessLifecycleOwner.get())),or
MarkerModifierhandler when the map composable leaves thecomposition.
Workaround
Pin
material3to1.5.0-alpha14(or any version that does not pull incompose-ui 1.10.0):This overrides the Compose BOM and avoids the breaking
compose-uichangeuntil
maps-composeis fixed.Environment
maps-composemaps-compose-utilsandroid-maps-utilscompose-ui(crashing)material3 1.5.0-alpha15)compose-ui(working)material3 1.5.0-alpha14)Compose BOMmaps-composeclustering APIClustering()composable with customclusterContent+clusterItemContentNonHierarchicalViewBasedAlgorithmminSdkcompileSdkAdditional Notes
rendering (i.e.,
clusterContent/clusterItemContentlambdas are set),because that activates
ComposeUiClusterRendererinstead of the defaulticon-based renderer.
if the user waits for cluster rendering to complete before navigating away.
compose-ui 1.10.0breakingchange —
maps-composeneeds to be updated to satisfy the new lifecyclerequirement.