codelab-android-compose icon indicating copy to clipboard operation
codelab-android-compose copied to clipboard

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

Open sandeepyohans opened this issue 2 years ago • 2 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()
    }
}

sandeepyohans avatar Feb 22 '23 15:02 sandeepyohans

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)

    )

Akhu avatar Apr 27 '23 16:04 Akhu

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

J6ey avatar Jan 06 '24 17:01 J6ey

Please read through the step 11, specifically:

"Note that we are also making sure that padding is never negative, otherwise it could crash the app. This introduces a subtle animation bug that we'll fix later in Finishing touches."

simona-anomis avatar Dec 09 '24 16:12 simona-anomis