compose-multiplatform: Crash "LayoutNode should be attached to an owner"

Describe the bug

I have a column with a list created from a for loop.

Each item in the list is wrapped with a call to key. This follows the example in the official docs for key here

Each item is a button that allows removing its own item from the list.

When using this, whether it works correctly or not depends on the speed of how fast you remove items. If you remove them slowly, it will work perfectly as expected. But, if you hover your mouse over the first button and rapidly click to remove them, you will get the following error. The exception seems to be handled internally and is not raised to the user. However, it is printed to the console, indicating to the developer that something is wrong.

java.lang.IllegalStateException: LayoutNode should be attached to an owner
	at androidx.compose.ui.node.LayoutNodeKt.requireOwner(LayoutNode.kt:1448)
	at androidx.compose.ui.node.LayoutNode.getCollapsedSemantics$ui(LayoutNode.kt:421)
	at androidx.compose.ui.semantics.SemanticsNodeKt.SemanticsNode(SemanticsNode.kt:48)
	at androidx.compose.ui.semantics.SemanticsNode.fillOneLayerOfSemanticsWrappers(SemanticsNode.kt:252)
	at androidx.compose.ui.semantics.SemanticsNode.fillOneLayerOfSemanticsWrappers(SemanticsNode.kt:254)
	at androidx.compose.ui.semantics.SemanticsNode.unmergedChildren$ui(SemanticsNode.kt:236)
	at androidx.compose.ui.semantics.SemanticsNode.unmergedChildren$ui$default(SemanticsNode.kt:229)
	at androidx.compose.ui.semantics.SemanticsNode.findOneLayerOfMergingSemanticsNodes(SemanticsNode.kt:343)
	at androidx.compose.ui.semantics.SemanticsNode.findOneLayerOfMergingSemanticsNodes$default(SemanticsNode.kt:340)
	at androidx.compose.ui.semantics.SemanticsNode.getChildren(SemanticsNode.kt:307)
	at androidx.compose.ui.semantics.SemanticsNode.getReplacedChildren$ui(SemanticsNode.kt:281)
	at androidx.compose.ui.platform.ComposeAccessible$ComposeAccessibleComponent.getAccessibleChildrenCount(ComposeAccessible.kt:299)
	at java.desktop/sun.lwawt.macosx.CAccessibility$23.call(CAccessibility.java:486)
	at java.desktop/sun.lwawt.macosx.CAccessibility$23.call(CAccessibility.java:470)
	at java.desktop/sun.lwawt.macosx.LWCToolkit$CallableWrapper.run(LWCToolkit.java:698)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:308)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:97)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:747)
	at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:744)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Affected platforms

  • Desktop

I have only tested Desktop. This may happen on other platforms as well, but I have not checked.

Versions

  • Kotlin version*: 1.9.20-Beta2
  • Compose Multiplatform version*: Gradle plugin version 1.5.10-beta01, compiler version 1.5.2.1-Beta2
  • OS version(s)* (required for Desktop and iOS issues): MacOs 13.5.2
  • OS architecture (x86 or arm64): arm64
  • JDK (for desktop issues): Temurin 18

To Reproduce

  1. Copy the code below, in a project with the versions above
  2. Run the application
  3. Hover your mouse of “Remove 1”
  4. Slowly click the button once per second until all buttons are gone
  5. Observe that there is no error
  6. Restart the app
  7. Repeat steps 3 and 4, but this time click as rapidly as you can
  8. Observe the error output with the IllegalStateException that was thrown
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application

fun main() {
    val list = mutableStateListOf(*(1..10).toList().toTypedArray())
    application {
        Window(visible = true, onCloseRequest = {
            exitApplication()
        }) {
            Column {
                for (i in list) {
                    key(i) {
                        Button(
                            onClick = { list.remove(i) }
                        ) {
                            Text("Remove $i")
                        }
                    }
                }
            }
        }
    }
}

Expected behavior

There should not be an error.

About this issue

  • Original URL
  • State: closed
  • Created 9 months ago
  • Reactions: 1
  • Comments: 25 (5 by maintainers)

Most upvoted comments

@lihenggui Original? Do you mean Android?

Yes, I am dealing with this issue now.

java.lang.IllegalStateException: LayoutNode should be attached to an owner

From what I see, the way to reproduce this issue is

  1. Create a LazyColumn
  2. Add items that exceed one page. Please note that these items can change their visibility by using AnimatedVisibility
  3. Click buttons to change the visibility of these items

I can see the same issue happening, but I don’t know if it relates to my usage.

PR that contains this bug: https://github.com/lihenggui/blocker/pull/377/files

Eventually i found out that the problem is in the alpha version of androidx.compose.material3:material3:1.2.0. on version 1.1.2 i could not reproduce the bug

I am also experiencing the same problem on Android. Kotlin version 1.9.10, compose version 1.5.1. Is there any action on Android other than the desktop platform?

Fatal Exception: java.lang.IllegalStateException
LayoutNode should be attached to an owner
androidx.compose.ui.node.LayoutNodeKt.requireOwner (LayoutNode.kt:1434)
androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:547)
androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0 (LayoutNodeLayoutDelegate.kt:539)
androidx.compose.ui.layout.RootMeasurePolicy.measure-3p2s80s (RootMeasurePolicy.kt:38)
androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0 (InnerNodeCoordinator.kt:106)
androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke (LayoutNodeLayoutDelegate.kt:1499)
androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke (LayoutNodeLayoutDelegate.kt:1495)
androidx.compose.runtime.snapshots.Snapshot$Companion.observe (Snapshot.kt:2299)
androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe (SnapshotStateObserver.kt:462)
androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads (SnapshotStateObserver.kt:230)
androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release (OwnerSnapshotObserver.kt:133)
androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release (OwnerSnapshotObserver.kt:113)
androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:1495)
androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:35)
androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:560)
androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release (LayoutNode.kt:1126)
androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA (MeasureAndLayoutDelegate.kt:321)
androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded (MeasureAndLayoutDelegate.kt:458)
androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded (MeasureAndLayoutDelegate.kt)
androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout (MeasureAndLayoutDelegate.kt:344)
androidx.compose.ui.platform.AndroidComposeView.measureAndLayout (AndroidComposeView.android.kt:879)
androidx.compose.ui.node.Owner.measureAndLayout$default (Owner.kt:223)
androidx.compose.ui.platform.AndroidComposeView.dispatchDraw (AndroidComposeView.android.kt:1127)
android.view.View.draw (View.java:20341)
android.view.View.draw (View.java:20203)
android.view.ViewGroup.drawChild (ViewGroup.java:4421)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4207)
android.view.View.updateDisplayListIfDirty (View.java:19274)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4405)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4385)
android.view.View.updateDisplayListIfDirty (View.java:19242)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4405)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4385)
android.view.View.updateDisplayListIfDirty (View.java:19242)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4405)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4385)
android.view.View.updateDisplayListIfDirty (View.java:19242)
android.view.ThreadedRenderer.updateViewTreeDisplayList (ThreadedRenderer.java:686)
android.view.ThreadedRenderer.updateRootDisplayList (ThreadedRenderer.java:692)
android.view.ThreadedRenderer.draw (ThreadedRenderer.java:800)
android.view.ViewRootImpl.draw (ViewRootImpl.java:3488)
android.view.ViewRootImpl.performDraw (ViewRootImpl.java:3275)
android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:2810)
android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:1779)
android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:7810)
android.view.Choreographer$CallbackRecord.run (Choreographer.java:911)
android.view.Choreographer.doCallbacks (Choreographer.java:723)
android.view.Choreographer.doFrame (Choreographer.java:658)
android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:897)
android.os.Handler.handleCallback (Handler.java:789)
android.os.Handler.dispatchMessage (Handler.java:98)
android.os.Looper.loop (Looper.java:164)
android.app.ActivityThread.main (ActivityThread.java:6938)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:327)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1374)

This bug also occurs in the original version of Compose.

Eventually i found out that the problem is in the alpha version of androidx.compose.material3:material3:1.2.0. on version 1.1.2 i could not reproduce the bug

I tried the latest material3 1.2.0-alpha10 and the problem disappear in my case. Previously used 1.2.0-alpha07