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
15.99k stars 1.16k forks source link

Compose multiplatfrom and jetpack compose compatibility problem (java.lang.NoSuchMethodError MeasureScope#layout$default) #5020

Closed firelion9 closed 6 days ago

firelion9 commented 3 months ago

Describe the bug Android application crashes after calling multiplatform composable function which itself calls MeasureScope#layout$default without passing alignmentLines parameter. The crash is reproducible when using compose multiplatform 1.6.11 and jetpack compose 1.6.7 (which are said to compatible (docs))

Affected platforms

Versions

To Reproduce Simple project reproducing the issue (the stacktrace at the end was taken from this app): https://github.com/firelion9/compose-issue

Steps to reproduce the behavior:

  1. Write the following function in compose multiplatform project:
    @Composable
    fun JbBox(modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit = {}) {
        Box(
            modifier = modifier
                .layout { measurable, constraints ->
                    val placeable = measurable.measure(constraints)
                    layout(placeable.width, placeable.height) { // <--- the problem rises here
                        placeable.place(0, 0)
                    }
                },
            content = content
        )
    }
  2. Use it in in android app:
    // in Activity#onCreate: 
    // ...
    setContent {
    JbBox {
        Text(text = "Everything is ok", fontSize = 24.sp)
    }
    }
    // ...
  3. Assemble the android app using ./gradlew assemble
  4. Install the app and launch the activity

Actual behavior App crashes with the following stacktrace:

stacktrace
  java.lang.NoSuchMethodError: No static method layout$default(Landroidx/compose/ui/layout/MeasureScope;IILjava/util/Map;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Landroidx/compose/ui/layout/MeasureResult; in class Landroidx/compose/ui/layout/MeasureScope; or its super classes (declaration of 'androidx.compose.ui.layout.MeasureScope' appears in /data/app/~~LU3_khy9PGLqWFNT6qOyTw==/com.firelion.composeissue--IW9u4BB3tq5PIncLBCMPw==/base.apk)
      at com.firelion.composeissue.jb.JbBoxKt.JbBox$lambda$1(JbBox.kt:15)
      at com.firelion.composeissue.jb.JbBoxKt.$r8$lambda$Qhg8UchGWaQufvJNnzws3rlvJ3A(Unknown Source:0)
      at com.firelion.composeissue.jb.JbBoxKt$$ExternalSyntheticLambda1.invoke(D8$$SyntheticClass:0)
      at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifier.kt:294)
      at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251)
      at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2303)
      at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:500)
      at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:256)
      at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
      at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:113)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:596)
      at androidx.compose.ui.layout.RootMeasurePolicy.measure-3p2s80s(RootMeasurePolicy.kt:38)
      at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251)
      at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2303)
      at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:500)
      at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:256)
      at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
      at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:113)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
      at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620)
      at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release(LayoutNode.kt:1145)
      at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:354)
      at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureOnly(MeasureAndLayoutDelegate.kt:562)
      at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureOnly(MeasureAndLayoutDelegate.kt:407)
      at androidx.compose.ui.platform.AndroidComposeView.onMeasure(AndroidComposeView.android.kt:1058)
      at android.view.View.measure(View.java:27122)
      at androidx.compose.ui.platform.AbstractComposeView.internalOnMeasure$ui_release(ComposeView.android.kt:302)
      at androidx.compose.ui.platform.AbstractComposeView.onMeasure(ComposeView.android.kt:289)
      at android.view.View.measure(View.java:27122)
      at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7008)
      at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
      at android.view.View.measure(View.java:27122)
      at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7008)
      at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1608)
      at android.widget.LinearLayout.measureVertical(LinearLayout.java:878)
      at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
      at android.view.View.measure(View.java:27122)
      at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7008)
      at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
      at com.android.internal.policy.DecorView.onMeasure(DecorView.java:750)
      at android.view.View.measure(View.java:27122)
      at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:4182)
      at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:2759)
      at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3086)
      at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2465)
      at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9305)
      at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1339)
      at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1348)
      at android.view.Choreographer.doCallbacks(Choreographer.java:952)
      at android.view.Choreographer.doFrame(Choreographer.java:882)
      at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1322)
      at android.os.Handler.handleCallback(Handler.java:958)
      at android.os.Handler.dispatchMessage(Handler.java:99)
      at android.os.Looper.loopOnce(Looper.java:205)
      at android.os.Looper.loop(Looper.java:294)
      at android.app.ActivityThread.main(ActivityThread.java:8177)
      at java.lang.reflect.Method.invoke(Native Method)
      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
  

Expected behavior The activity launches and shows label "Everything is ok".

Additional context The problem is not reproducible without using compose multiplatform (if you move JbBox to android module) and when you compile and run application using android studio's "run" action

kropp commented 3 months ago

There are two overloads for layout on Android, and just a single one in Compose Multiplatform. Judging from the exception, it seems that some multiplatform artifact is packed into an android app, while during the compilation the correct one is used. Can you check the dependencies, please? Unfortunately, I wasn't able to reproduce this issue on the sample project on my machine.

firelion9 commented 3 months ago

I checked my dependencies, everything looks fine: all compose mutiplatform dependencies are compile-only and androidx.compose.ui:ui-android:1.6.7 is presented in both compile and runtime classpaths of app module. I also checked MeasureScope definition in android and desktop variants of androidx.compose.ui:ui and they both contain single layout overload:

fun layout(
        width: Int,
        height: Int,
        alignmentLines: Map<AlignmentLine, Int> = emptyMap(),
        placementBlock: Placeable.PlacementScope.() -> Unit
    ): MeasureResult { /* ... */ }

Are you sure we are speaking about the same layout?

Unfortunately, I currently have access to a single windows machine only, but I tried to compile the sample project again from a clean checkout on it with the same result: successful run with android studio's run action and crash when compiling with ./gradlew :app:assembleDebug or with "generate signed app bundle / apk".

I also inspected generated apks and found out that in crashing apks required method is presented in MeasureScope$-CC instead of MeasureScope, but I still don't understand why it happens and why it doesn't happen with android studio's run action

okushnikov commented 1 month ago

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