android / codelab-android-compose

Apache License 2.0
1.46k stars 666 forks source link

java.lang.IllegalArgumentException: Padding must be non-negative #336

Open sandeepyohans opened 1 year ago

sandeepyohans commented 1 year ago

I am trying the Compose Basic Codelab example. After adding the animation, the app crashes whenever I click on "Show more" / "Show less" button.

Codelab : https://developer.android.com/codelabs/jetpack-compose-basics#10

Error Log: 21:00:29.520 E FATAL EXCEPTION: main Process: com.example.composebasiccodelab1, PID: 27406 java.lang.IllegalArgumentException: Padding must be non-negative at androidx.compose.foundation.layout.PaddingModifier.<init>(Padding.kt:346) at androidx.compose.foundation.layout.PaddingModifier.<init>(Unknown Source:0) at androidx.compose.foundation.layout.PaddingKt.padding-qDBjuR0(Padding.kt:56) at androidx.compose.foundation.layout.PaddingKt.padding-qDBjuR0$default(Padding.kt:50) at com.example.composebasiccodelab1.MainActivityKt$Greeting$1.invoke(MainActivity.kt:107) at com.example.composebasiccodelab1.MainActivityKt$Greeting$1.invoke(MainActivity.kt:103) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107) at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34) at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:145) at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2351) at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2618) at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3205) at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3183) at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:252) at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1) at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3183) at androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:3148) at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:746) at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:876) at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:107) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:485) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:454) 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:947) at android.view.Choreographer.doCallbacks(Choreographer.java:761) at android.view.Choreographer.doFrame(Choreographer.java:693) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935) at android.os.Handler.handleCallback(Handler.java:873) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:6669) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.runtime.PausableMonotonicFrameClock@87eb08d, androidx.compose.ui.platform.MotionDurationScaleImpl@1ca5242, StandaloneCoroutine{Cancelling}@9415253, AndroidUiDispatcher@7178790]

This is my MainActivity:

package com.example.composebasiccodelab1

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.composebasiccodelab1.ui.theme.ComposeBasicCodelab1Theme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.runtime.getValue
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBasicCodelab1Theme {
                MyApp(modifier = Modifier.fillMaxSize())
            }
        }
    }
}

@Composable
private fun MyApp(modifier: Modifier = Modifier) {
    var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }

    // A surface container using the 'background' color from the theme
    Surface(
        modifier = modifier,
        color = MaterialTheme.colorScheme.background
    ) {
        if(shouldShowOnboarding)
            OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
        else
            Greetings()
    }
}

@Composable
fun OnboardingScreen(modifier: Modifier = Modifier, onContinueClicked: () -> Unit) {
    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Welcome to the Basics Codelab!")
        Button(
            modifier = Modifier.padding(vertical = 24.dp),
            onClick = onContinueClicked
        ) {
            Text("Continue")
        }
    }
}

@Composable
fun Greetings(
    modifier: Modifier = Modifier,
    names: List<String> = List(1000) { "$it" }
) {
    LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
        items( items = names) {
            Greeting(name = it)
        }
    }
}

@Composable
fun Greeting(name: String) {
    var expanded by remember { mutableStateOf(false) }
    val extraPadding by animateDpAsState(
        if(expanded) 48.dp else 0.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessLow
        )
    )
    Surface(
        modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp),
        color = MaterialTheme.colorScheme.primary
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(modifier = Modifier
                .weight(1f)
                .padding(bottom = extraPadding)) {
                Text(text = "Hello, ")
                Text(text = name)
            }
            ElevatedButton(onClick = { expanded= !expanded } ) {
                Text(text = if(expanded)  "Show less" else "Show more")
            }
        }
    }
}

@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
    ComposeBasicCodelab1Theme {
        OnboardingScreen(onContinueClicked = { })
    }
}

@Preview(showBackground = true, widthDp = 320)
@Composable
fun DefaultPreview() {
    ComposeBasicCodelab1Theme {
        MyApp()
    }
}
Akhu commented 1 year ago

Got the same, I think this is related to the spring animation that interpolate the padding to a negative number for the sake of the spring animation (for rebounce effect)

Which mean this is a bug within compose itself

image To fix :

val extraPadding by animateDpAsState(
        if (expanded) 48.dp else 0.dp,
        animationSpec = tween(durationMillis = 300)

    )
J6ey commented 10 months ago

The solution, as shown further into the tutorial, is to use coerceAtLeast(minimumValue: T) to ensure this value is not less than specified minimumValue.

            Column(modifier = Modifier
                .weight(1f)
                .padding(bottom = extraPadding.coerceAtLeast(0.dp))) {
                Text(text = "Hello, ")
                Text(text = name)
            }