androidx / constraintlayout

ConstraintLayout is an Android layout component which allows you to position and size widgets in a flexible way
Apache License 2.0
1.07k stars 176 forks source link

MotionLayout is crashing #526

Open robertlevonyan opened 2 years ago

robertlevonyan commented 2 years ago

The motion layout is crashing with this error.

java.lang.IllegalArgumentException: Failed requirement.
        at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure(MeasureAndLayoutDelegate.kt:177)
        at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:228)
        at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:38)
        at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:201)
        at androidx.compose.ui.platform.AndroidComposeView.measureAndLayout(AndroidComposeView.android.kt:662)
        at androidx.compose.ui.node.Owner$DefaultImpls.measureAndLayout$default(Owner.kt:182)
        at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:846)
        at android.view.View.draw(View.java:22648)
        at android.view.View.updateDisplayListIfDirty(View.java:21520)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
        at android.view.View.updateDisplayListIfDirty(View.java:21476)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
        at android.view.View.updateDisplayListIfDirty(View.java:21476)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
        at android.view.View.updateDisplayListIfDirty(View.java:21476)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
        at android.view.View.updateDisplayListIfDirty(View.java:21476)
        at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:534)
        at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:540)
        at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:616)
        at android.view.ViewRootImpl.draw(ViewRootImpl.java:4438)
        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4166)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3326)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2143)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8665)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1037)
        at android.view.Choreographer.doCallbacks(Choreographer.java:845)
        at android.view.Choreographer.doFrame(Choreographer.java:780)
        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)

this is my code

  var sectionsState: Boolean by rememberSaveable { mutableStateOf(false) }

  val collapsedSet = getCollapsedConstraints()
  val expandedSet = getExpandedConstraints()

  val progress by animateFloatAsState(
    targetValue = if (sectionsState) 1f else 0f,
    animationSpec = tween(150)
  )

MotionLayout(
      start = collapsedSet,
      end = expandedSet,
      progress = progress,
      modifier = Modifier.fillMaxSize(),
    ) {
...
}

private fun getCollapsedConstraints(): ConstraintSet = ConstraintSet {
  val title = createRefFor("title")
  val subtitle = createRefFor("subtitle")
  val toggle = createRefFor("toggle")
  val sections = createRefFor("sections")
  val timers = createRefFor("timers")

  constrain(title) {
    width = Dimension.wrapContent
    height = Dimension.wrapContent
    end.linkTo(parent.end)
    start.linkTo(parent.start)
    top.linkTo(parent.top)
  }
  constrain(subtitle) {
    width = Dimension.wrapContent
    height = Dimension.wrapContent
    end.linkTo(parent.end)
    start.linkTo(parent.start)
    top.linkTo(title.bottom)
  }
  constrain(sections) {
    width = Dimension.matchParent
    height = Dimension.wrapContent
    start.linkTo(parent.start)
    end.linkTo(parent.end)
    bottom.linkTo(parent.top)
  }
  constrain(toggle) {
    end.linkTo(parent.end)
    top.linkTo(sections.bottom)
  }
  constrain(timers) {
    width = Dimension.wrapContent
    height = Dimension.wrapContent
    start.linkTo(parent.start)
    end.linkTo(parent.end)
    bottom.linkTo(parent.bottom)
  }
}

private fun getExpandedConstraints(): ConstraintSet = ConstraintSet {
  val title = createRefFor("title")
  val subtitle = createRefFor("subtitle")
  val toggle = createRefFor("toggle")
  val sections = createRefFor("sections")
  val timers = createRefFor("timers")

  constrain(title) {
    width = Dimension.wrapContent
    height = Dimension.wrapContent
    end.linkTo(parent.end)
    start.linkTo(parent.start)
    top.linkTo(parent.top)
  }
  constrain(subtitle) {
    width = Dimension.wrapContent
    height = Dimension.wrapContent
    end.linkTo(parent.end)
    start.linkTo(parent.start)
    top.linkTo(title.bottom)
  }
  constrain(sections) {
    width = Dimension.matchParent
    height = Dimension.wrapContent
    start.linkTo(parent.start)
    end.linkTo(parent.end)
    top.linkTo(parent.top)
  }
  constrain(toggle) {
    end.linkTo(parent.end)
    top.linkTo(sections.bottom)
  }
  constrain(timers) {
    width = Dimension.wrapContent
    height = Dimension.wrapContent
    start.linkTo(parent.start)
    end.linkTo(parent.end)
    bottom.linkTo(parent.bottom)
  }
}
oscar-ad commented 2 years ago

Which Compose version is this?

Haven't been able to repro on 1.0.0 or even 1.1.0.

I'm using a very simple setup:

@OptIn(ExperimentalMotionApi::class)
@Preview
@Composable
fun CrashTest() {
    var sectionsState: Boolean by rememberSaveable { mutableStateOf(false) }

    val collapsedSet = getCollapsedConstraints()
    val expandedSet = getExpandedConstraints()

    val progress by animateFloatAsState(
        targetValue = if (sectionsState) 1f else 0f,
        animationSpec = tween(150)
    )

    Column {
        MotionLayout(
            start = collapsedSet,
            end = expandedSet,
            progress = progress,
            modifier = Modifier.fillMaxWidth().weight(1.0f, true),
        ) {
            Text(
                text = "My Title",
                Modifier
                    .layoutId("title")
                    .background(Color.Red))
            Text(
                text = "My subtitle",
                Modifier
                    .layoutId("subtitle")
                    .background(Color.Blue))
            Text(
                text = "TOGGLE",
                Modifier
                    .layoutId("toggle")
                    .background(Color.Cyan))
            Text(
                text = "Sections",
                Modifier
                    .layoutId("sections")
                    .background(Color.Yellow))
            Text(
                text = "Timers:",
                Modifier
                    .layoutId("timers")
                    .background(Color.Gray))
        }
        Button(onClick = { sectionsState = !sectionsState }) {
            Text(text = "Run")
        }
    }
}
oscar-ad commented 2 years ago

Any chance one of the Composables in MotionLayout has a size Modifier? (size, sizeIn, defaultMin, fillMaxSize, etc)

Does it still crash with Dimension.fillToConstraints instead of Dimension.matchParent?

Eric-Cen commented 2 years ago

Here is an example that shows the same error message, https://github.com/jipariz/ComposeMotion from the article, https://www.strv.com/blog/collapsing-toolbar-using-jetpack-compose-motion-layout-engineering.

Based on this ComposeMotion project, update the followings: 1) compose_version = '1.1.1' 2) classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" 3) distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip

Then compile and run. The exact same code works on older Compose versions, 1.0.1 and 1.04. However, it doesn't work with 1.1.1 with the error message. Thanks.

smithc42 commented 2 years ago

Also had this issue today using the repo @Eric-Cen linked. @oscar-ad do you know if there's any workaround for this?

oscar-ad commented 2 years ago

No workaround other than sticking to 1.0.x

It's a hard check that prevents us from remeasuring Composables during animation in some cases.

I'll close this once we work out a solution with the rest of the Compose team.

oscar-ad commented 2 years ago

Issue seems to be resolved when using 1.2.0-alpha08 Tho I see issues with custom properties.

I'll keep this open until 1.2.0 hits stable or I'm able to guarantee no regressions from there.