compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

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

Open firelion9 opened this issue 1 year ago • 1 comments

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

  • Android The issue affects only android platform, but it seems that the problem is not in jetpack compose

Versions

  • Libraries:

    • Compose Multiplatform version: 1.6.11
    • Jetpack Compose version: 1.6.7
  • Kotlin version: 2.0.0

  • OS versions: Android 14 (both hardware and emulator)

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)
  }
}
// ...
  1. Assemble the android app using ./gradlew assemble
  2. 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

firelion9 avatar Jun 24 '24 18:06 firelion9

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.

kropp avatar Jun 27 '24 09:06 kropp

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

firelion9 avatar Jul 01 '24 08:07 firelion9

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

okushnikov avatar Aug 26 '24 13:08 okushnikov