1

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

  • 1
    Scaffold is designed as a top level component. It fills max size by default. So I think putting it inside scrollable component is not possible without specifying its size directly. – bylazy Feb 15 '22 at 12:56
  • @bylazy What do you think about this abstraction approach to make the custom view with scaffold ? – Sepideh Vatankhah Feb 15 '22 at 13:16
  • 1
    I would replace Scaffold with something more appropriate. In fact, you do not use its main advantages, such as dockable FAB or Snackbars. So, here it is just as layout. What you want to do, as I understand it, can easily be done with a Сolumn and three Rows inside. – bylazy Feb 15 '22 at 13:57
  • 1
    @bylazy I got your point, you mean keep the DynamicTile func and replace scaffold with column and rows then I have multi slot API function and also no need to add fixed size to each card – Sepideh Vatankhah Feb 15 '22 at 14:31

1 Answers1

2

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

42Geek
  • 332
  • 3
  • 7