kotlinandroid-jetpack-composevertical-scrollingandroid-jetpack-compose-scaffold

Jetpack Compose,I have multiple cards have scaffold inside, in a vertical scrollable column and if you don't give the card specific height it crashes


In our application, there are many custom views that all are card with 3 slots header, content and bottom part, so I thought we can handle it with a scaffold inside the card except having many if/else conditions

So I created this base composable function ->

@Composable
fun DynamicTile(
    modifier: Modifier = Modifier,
    headContent: @Composable () -> Unit,
    mainContent: @Composable (PaddingValues) -> Unit = { },
    bottomContent: @Composable () -> Unit = { }
) {
    Card( modifier = modifier) {
        Scaffold(
            topBar = headContent,
            content = mainContent,
            bottomBar = bottomContent
        )
    }
}

then different implementation for different purposes, I mention here two purposes e.g. showing specific image, animation, map and ... ->

for this one you should add your local drawable to image to compile it

@Composable
fun TileTeaser( modifier: Modifier = Modifier) {
    with(entity) {
        DynamicTile(
//            modifier = modifier.then(Modifier.height(250.dp)),
            headContent = {
                Text(
                    text = "headline",
                   
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(start = 16.dp, end = 16.dp, top = 8.dp)
                )
            },
            mainContent = {
                 val painter = rememberImagePainter(
                    data = painterResource(R.drawable.ds_ic_add))
                Image(
                    contentScale = ContentScale.Crop,
                    painter = painter,
                    modifier = modifier,
                    contentDescription = null
                )
            },
            bottomContent = {
                     Box(modifier = Modifier.fillMaxWidth()) {
                    Button(
                        onClick = { }, modifier = Modifier
                            .align(Alignment.Center)
                            .wrapContentSize()
                    ) {
                        Text(text = "Button")
                    }
                }
                }
            }
        )
    }
}

And this one for animation with Lottie, you should add local raw to compile it

@Composable
fun TileAnimation(modifier: Modifier = Modifier) {
    DynamicTile(
        modifier = modifier.then(Modifier.height(300.dp)),
        headContent = {
            Text(
                text = "headline",
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(start = 16.dp, end = 16.dp, top = 8.dp)
            )
        },
        mainContent = {
            val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.sth))
            Card(
                modifier = Modifier
                    .height(183.dp)
                    .then(modifier),
                shape = RoundedCornerShape(0.dp)
            ) {
                LottieAnimation(
                    composition = composition,
                    modifier = modifier
                        .fillMaxSize(),
                    contentScale = ContentScale.Crop,
                )
            }
        },
        bottomContent = {
            Box(modifier = Modifier.fillMaxWidth()) {
                Button(
                    onClick = { }, modifier = Modifier
                        .align(Alignment.Center)
                        .wrapContentSize()
                ) {
                    Text(text = "Button")
                }
            }
        }
    )
}

Then I load them in column like this

@Composable
fun LoadScreen() {
    Column(
        modifier = Modifier
            .padding(16.dp)
            .verticalScroll(rememberScrollState())
    ) {
        Text(text = "Teaser")

        TileTeaser(
            modifier = Modifier.padding(top = 16.dp)
        )
        
        Text(
            text = "Animation",
            modifier = Modifier.padding(top = 16.dp)
        )

        TileAnimation(modifier = Modifier.padding(vertical = 16.dp))
    }
}

As you see if I comment the Modifier.height of the card, it crashes with this error ->

  java.lang.IllegalArgumentException: Can't represent a size of 2147483563 in Constraints
        at androidx.compose.ui.unit.Constraints$Companion.bitsNeedForSize(Constraints.kt:408)
        at androidx.compose.ui.unit.Constraints$Companion.createConstraints-Zbe2FdA$ui_unit_release(Constraints.kt:368)

Kotlin version 1.6.10 and compose 1.1.0 and this is lottie library ->

implementation "com.airbnb.android:lottie-compose:4.2.2"

BTW, you can download Lottie file from here

Thank you in advance for your help


Solution

  • TL;DR Don't put a Scaffold inside a Card inside a scrollable content. :)

    If we take a deeper look into Scaffold code we will see that it's actually creating a ScaffoldLayout that will create a SubcomposeLayout that uses constraints and more specifically the following width and height:

    val layoutWidth = constraints.maxWidth
    val layoutHeight = constraints.maxHeight
    

    Now, if no predefined values was set to the above layoutWidth and layoutHeight they will be equal to Int.MAX_VALUE which is 2147483647 which is what you see in your IllegalArgumentException (give or take).

    When you use Scaffold the right way, usually as the root of your UI, Android do the magic for you and calculate the correct size.

    My suggestion, replace the Scaffold with another type of layout or if it's not enough a custom layout:

    https://developer.android.com/jetpack/compose/layouts/custom