s3rius
05/27/2024, 12:54 PMplaceholder
modifier from Accompanist, but updated for the new Modifier.node
system since Accompanist has deprecated theirs.
However, I'm sometimes getting a crash because the coroutineScope couldn't be created.
There's not terribly much documentation about this new system, so I'm unsure how to tackle this. Code and some details are in the reply đź§µs3rius
05/27/2024, 12:57 PMcoroutineScope
sometimes crashes, as described.
I'm calling it from two places: Node.update()
and ModifierNodeElement.onAttach()
. The first one doesn't explicitly say that calling coroutineScope
is allowed, but at this point the node should be attached to a layout and thus have an owner. The second one explicitly says that calling coroutineScope
is allowed.
private class MyNode(
var foo: Boolean
) : Modifier.Node() {
fun update() {
// This calls requireOwner().coroutineContext
// which rarely crashes with java.lang.IllegalStateException: This node does not have an owner.
coroutineScope.launch { /* do stuff */ }
}
/**
* Documentation of onAttach reads:
* When called, `node` is guaranteed to be non-null. You can call sideEffect,
* coroutineScope, etc.
*/
override fun onAttach() {
update()
}
}
private data class MyElement(
val foo: Boolean
) : ModifierNodeElement<MyNode>() {
override fun create() = MyNode(foo)
/**
* Documentation of update reads:
* Called when a modifier is applied to a Layout whose inputs have changed
*/
override fun update(node: MyNode) {
node.foo = foo
node.update()
}
}
s3rius
05/27/2024, 1:00 PMfoo
in this example) may require discarding the old coroutine and starting a new one.
(As I said, the modifier is placeholder
, so think of an input like animationDuration
changing, which requires starting a new coroutine with animatable.animateTo(..., animationDuration)
)Louis Pullen-Freilich [G]
05/27/2024, 4:25 PMStylianos Gakis
05/27/2024, 5:01 PMs3rius
05/27/2024, 6:06 PMLouis Pullen-Freilich [G]
05/27/2024, 6:11 PMLouis Pullen-Freilich [G]
05/27/2024, 6:11 PMLouis Pullen-Freilich [G]
05/27/2024, 6:12 PMs3rius
05/29/2024, 2:01 PMProcess: <http://com.example.app|com.example.app>, PID: 2006
java.lang.IllegalStateException: This node does not have an owner.
at androidx.compose.ui.internal.InlineClassHelperKt.throwIllegalStateExceptionForNullCheck(InlineClassHelper.kt:30)
at androidx.compose.ui.node.DelegatableNodeKt.requireOwner(DelegatableNode.kt:1291)
at androidx.compose.ui.Modifier$Node.getCoroutineScope(Modifier.kt:207)
at com.example.app.shimmer.ShimmerNode.update(Shimmer.kt:178)
at com.example.app.shimmer.ShimmerElement.update(Shimmer.kt:76)
at com.example.app.shimmer.ShimmerElement.update(Shimmer.kt:62)
at androidx.compose.ui.node.NodeChainKt.updateUnsafe(NodeChain.kt:830)
at androidx.compose.ui.node.NodeChainKt.access$updateUnsafe(NodeChain.kt:1)
at androidx.compose.ui.node.NodeChain.updateNode(NodeChain.kt:672)
at androidx.compose.ui.node.NodeChain.updateFrom$ui_release(NodeChain.kt:143)
at androidx.compose.ui.node.LayoutNode.setModifier(LayoutNode.kt:893)
at androidx.compose.ui.node.ComposeUiNode$Companion$SetModifier$1.invoke(ComposeUiNode.kt:47)
at androidx.compose.ui.node.ComposeUiNode$Companion$SetModifier$1.invoke(ComposeUiNode.kt:47)
at androidx.compose.runtime.changelist.Operation$UpdateNode.execute(Operation.kt:449)
at androidx.compose.runtime.changelist.Operations.executeAndFlushAllPendingOperations(Operations.kt:309)
at androidx.compose.runtime.changelist.ChangeList.executeAndFlushAllPendingChanges(ChangeList.kt:81)
at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:984)
at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:1013)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:678)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:578)
at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.android.kt:109)
at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1035)
at android.view.Choreographer.doCallbacks(Choreographer.java:845)
at android.view.Choreographer.doFrame(Choreographer.java:775)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.runtime.PausableMonotonicFrameClock@45acfef, androidx.compose.ui.platform.MotionDurationScaleImpl@7df8afc, StandaloneCoroutine{Cancelling}@16f2585, AndroidUiDispatcher@39fafda]