JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
16.21k stars 1.17k forks source link

java.lang.NullPointerException at androidx.compose.ui.node.DelegatableNodeKt.requireOwner(DelegatableNode.kt:308) #2505

Closed serhiynovos closed 1 year ago

serhiynovos commented 1 year ago

Hi. I'm playing with jetbrains compose and Decompose library https://github.com/arkivanov/Decompose using it's navigation

On one of the page I'm using .fillMaxWidth() modifier and when navigates to this page I'm getting the next exception.

The same code works fine on desktop version

E/AndroidRuntime: FATAL EXCEPTION: main Process: com.serhiy.connect, PID: 10207 java.lang.NullPointerException at androidx.compose.ui.node.DelegatableNodeKt.requireOwner(DelegatableNode.kt:308) at androidx.compose.ui.node.SemanticsModifierNodeKt.invalidateSemantics(SemanticsModifierNode.kt:44) at androidx.compose.ui.node.NodeKindKt.autoInvalidateNode(NodeKind.kt:171) at androidx.compose.ui.node.NodeChain.updateNodeAndReplaceIfNeeded(NodeChain.kt:513) at androidx.compose.ui.node.NodeChain.updateFrom$ui_release(NodeChain.kt:130) at androidx.compose.ui.node.LayoutNode.setModifier(LayoutNode.kt:735) at androidx.compose.ui.node.ComposeUiNode$Companion$SetModifier$1.invoke(ComposeUiNode.kt:42) at androidx.compose.ui.node.ComposeUiNode$Companion$SetModifier$1.invoke(ComposeUiNode.kt:42) at androidx.compose.runtime.ComposerImpl$apply$operation$1.invoke(Composer.kt:1621) at androidx.compose.runtime.ComposerImpl$apply$operation$1.invoke(Composer.kt:1619) at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:808) at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:839) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:585) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:503) at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:34) 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:1228) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1238) at android.view.Choreographer.doCallbacks(Choreographer.java:898) at android.view.Choreographer.doFrame(Choreographer.java:826) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1213) at android.os.Handler.handleCallback(Handler.java:942) 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:7892) 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:936) Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.runtime.PausableMonotonicFrameClock@bef7741, androidx.compose.ui.platform.MotionDurationScaleImpl@891c4e6, StandaloneCoroutine{Cancelling}@ff31a27, AndroidUiDispatcher@da434d4]

serhiynovos commented 1 year ago

Sorry, looks like it's not because of .fillMaxWidth modifier, it still crashes but less often.

dima-avdeev-jb commented 1 year ago

@serrg1994 Hello! Can you please provide reproducible sample on GitHub?

serhiynovos commented 1 year ago

Yes. I can do it. Also I asked Decompose author and he sent me a link with the same issue on Google issues tracker https://issuetracker.google.com/issues/258907850

serhiynovos commented 1 year ago

@dima-avdeev-jb Hi. Created simple demo app to reproduce it https://github.com/serrg1994/kmm-issues-testing

I still was able to reproduce it, but in this project it required much more time to reproduce. On original project I was able to reproduce after 1 - 2 tries.

image

To reproduce it, open the app and try to navigate between pages. Uploaded video to show how I was able to reproduce it

https://user-images.githubusercontent.com/9201885/204378528-021277fe-01f2-43d4-9520-042dec32d490.mp4

serhiynovos commented 1 year ago

@dima-avdeev-jb @arkivanov I was playing on it and noticed if I remove navigation animation https://github.com/serrg1994/kmm-issues-testing/blob/main/common/src/commonMain/kotlin/com/example/common/App.kt#L28 it does not crash. With animation my original app crashes every time and after commenting this line I cannot reproduce it

arkivanov commented 1 year ago

@serrg1994 I tried hard reproducing the issue with your project, I have tried API 33 and API 30 emulators, spent a couple of minutes navigating through screens and couldn't reproduce. Wondering if this is something specific to your environment? What is the emulator your are testing on? Maybe worth trying a clean build - ./gradlew :android:assembleDebug --rerun-tasks --no-build-cache?

Meantime, here is the APK file I tested (zipped) - android-debug.apk.zip.

serhiynovos commented 1 year ago

@arkivanov I also was testing on real device with Android 12 and had identical behaviour, emulator is running Android 13

But yeah I noticed in source code I've published to GitHub is much more harder to reproduce it, sometimes I had to navigate between pages a lot of time to crash and sometimes I was not able to reproduce

dima-avdeev-jb commented 1 year ago

I reproduce it on my old Google Pixel 1 with Android 10

java.lang.NullPointerException
        at androidx.compose.ui.node.DelegatableNodeKt.requireOwner(DelegatableNode.kt:308)
        at androidx.compose.ui.node.SemanticsModifierNodeKt.invalidateSemantics(SemanticsModifierNode.kt:44)
        at androidx.compose.ui.node.NodeKindKt.autoInvalidateNode(NodeKind.kt:171)
        at androidx.compose.ui.node.NodeChain.updateNodeAndReplaceIfNeeded(NodeChain.kt:513)
        at androidx.compose.ui.node.NodeChain.updateFrom$ui_release(NodeChain.kt:130)
        at androidx.compose.ui.node.LayoutNode.setModifier(LayoutNode.kt:735)
        at androidx.compose.ui.node.ComposeUiNode$Companion$SetModifier$1.invoke(ComposeUiNode.kt:42)
        at androidx.compose.ui.node.ComposeUiNode$Companion$SetModifier$1.invoke(ComposeUiNode.kt:42)
        at androidx.compose.runtime.ComposerImpl$apply$operation$1.invoke(Composer.kt:1621)
        at androidx.compose.runtime.ComposerImpl$apply$operation$1.invoke(Composer.kt:1619)
        at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:808)
        at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:839)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:585)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:503)
        at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:34)
        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:964)
        at android.view.Choreographer.doCallbacks(Choreographer.java:790)
        at android.view.Choreographer.doFrame(Choreographer.java:721)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:951)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
serhiynovos commented 1 year ago

@dima-avdeev-jb Did it crash after first page navigation or you had to navigate between pages multiple times ?

Noticed if use slide animation for me it crashes more often

animation = stackAnimation(slide())

arkivanov commented 1 year ago

@serrg1994 so it still may crash without the animation?

serhiynovos commented 1 year ago

@serrg1994 so it still may crash without the animation?

Without animation I was not able to reproduce it

serhiynovos commented 1 year ago

@arkivanov without animation I was not able to reproduce neither on this test project or original one on which I started working. I don't know why, but my original project crashes more often, but only difference I see that for test project I removed http calls. If you want in the evening I can add http calls to test one to make it as close as possible to original one

dima-avdeev-jb commented 1 year ago

@dima-avdeev-jb Did it crash after first page navigation or you had to navigate between pages multiple times ?

Noticed if use slide animation for me it crashes more often

animation = stackAnimation(slide())

I navigate multiple times

arkivanov commented 1 year ago

@serrg1994 I tried to reproduce again on my Pixel 6 Pro, as well as on emulators with API 30, 32 and 33. Also tried building on macOS and Linux. It just works for me :-) Meantime, I noticed one possibly incorrect usage of Compose in Decompose animations, there is little chance it could fix the crash. I have assembled an APK with the fix, I will appreciate if someone checks if it still crashes. android-debug.apk.zip

dima-avdeev-jb commented 1 year ago

@arkivanov Exception still throws on my Pixel 1 with Android 10 in android-debug.apk.zip

arkivanov commented 1 year ago

Thanks for letting me know. I'm out of ideas for now.

dima-avdeev-jb commented 1 year ago

I made a reproduction sample, which throws the same Exception in 1 click: https://github.com/dima-avdeev-jb/kmm-issues-testing

dima-avdeev-jb commented 1 year ago
Children(
    stack = childStack,
    modifier = Modifier.fillMaxSize(),
    animation = stackAnimation(fade()) //TODO without animation, all work's fine
  ) {
    Box(
      modifier = Modifier.fillMaxSize()
    ) {
      when (val child = it.instance) {
        is Root.Child.A -> {
          Scaffold {//TODO without material3 Scaffold, all work's fine
            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
              Button(
                onClick = {
                  child.component.openAuthorizationPage()
                }) {
                Text("A", Modifier.padding(30.dp))
              }
            }
          }
        }
        is Root.Child.B -> {
          Text("Bug fixed, if you see this message")
        }
      }
    }
  }

I think the problem in material3 Scaffold with stackAnimation(fade()) together. If we remove one of them - all work's fine

dima-avdeev-jb commented 1 year ago

@arkivanov @serrg1994 I think, I found root of the problem. It is alpha version of material3. If we change library api("androidx.compose.material3:material3:1.1.0-alpha02") to stable version 1.0.1 - all will be fine!

arkivanov commented 1 year ago

Thanks @dima-avdeev-jb, I have reproduced the crash now. Turned out Material3 Scaffold crashes inside movableContentOf when it switches. I have added a simple reproducer to the mentioned AOSP bug. From my side, I will see if I can get rid of movableContentOf, but this might not be easy without API changes.

@Composable
fun App() {
    var isHorizontal by remember { mutableStateOf(false) }

    val content =
        remember {
            movableContentOf {
                Scaffold { paddingValues ->
                    Box(
                        modifier = Modifier.padding(paddingValues).fillMaxSize(),
                        contentAlignment = Alignment.Center,
                    ) {
                        Button(onClick = { isHorizontal = !isHorizontal }) {
                            Text("Click me")
                        }
                    }
                }
            }
        }

    if (isHorizontal) {
        Row { content() }
    } else {
        Column { content() }
    }
}
arkivanov commented 1 year ago

Oops, looks like I'm late with my results)) @dima-avdeev-jb do you mind adding your solution to the linked bug?

serhiynovos commented 1 year ago

@dima-avdeev-jb I'll try it on my side as well and will let you know

dima-avdeev-jb commented 1 year ago

@serrg1994 @arkivanov I've simplified our sample to only use the Android module. (https://github.com/dima-avdeev-jb/kmm-issues-testing)

So, I think this bug is about early version of material3:1.1.0-alpha02 @serrg1994 For now, you may use version material3:1.0.1

I will close this Issue, because it is already described here: https://partnerissuetracker.corp.google.com/issues/258907850 Also, I attach our sample to a Google team.

serhiynovos commented 1 year ago

@arkivanov @dima-avdeev-jb Thanks you

okushnikov commented 3 months ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.