Skip to content

ComposeUiClusterRenderer crashes with IllegalStateException: "Composed into the View which doesn't propagate ViewTreeLifecycleOwner!" on fast back gesture with compose-ui 1.10.0 #875

@jakubrzeznicki

Description

@jakubrzeznicki

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

  1. Use maps-compose 8.2.0 with the Clustering() composable.
  2. Configure a clusterContent or clusterItemContent lambda (custom Compose
    UI for clusters/markers) — this activates ComposeUiClusterRenderer.
  3. 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).
  4. Navigate to a screen that displays a GoogleMap with many markers so that
    clustering is still rendering in the background.
  5. Immediately perform a back gesture (predictive back or regular back)
    before the cluster renderer finishes processing all markers.
  6. 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.onMeasureensureCompositionCreated.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: p0Highest priority. Critical issue. P0 implies highest priority.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions