android/codelab-android-compose

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

sandeepyohans opened this issue · 3 comments

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

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

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)
            }