6

I have a LazyColumn and inside it I want to display a horizontal row with two columns, so I was trying LazyHorizontalGrid to achieve it. But my application crashes with the exception - IllegalArgumentException: LazyHorizontalGrid's height should be bound by parent. Below is my code what I am using, can anyone please help to fix it or any other way through which I can make a row have two columns.

@Composable
fun HomeItem1() {
    Surface(modifier = Modifier.nestedScroll(rememberViewInteropNestedScrollConnection())) {
        LazyColumn {
            //other contents
            item {
                LazyHorizontalGrid(
                    rows = GridCells.Fixed(3),
                    horizontalArrangement = Arrangement.spacedBy(16.dp),
                    verticalArrangement = Arrangement.spacedBy(16.dp)
                ) {
                    items(arrayList.size) {
                        Text(arrayList[it])
                    }
                }
            }
        }
    }
}
Mayur
  • 141
  • 6
  • did you find a solution? my app crashes too, because of this error. – mrzbn Dec 28 '22 at 07:04
  • @mrzbn no it is not possible to use LazyHorizontalGrid or other similar views inside another LazyColumn. Anyways I have used a work around without using LazyHorizontalGrid to achieve my requirement. Let me know if you want the work around. – Mayur Dec 28 '22 at 17:00
  • 1
    but I can use LazyRow inside LazyColumn. Why cant I use something similar to that. – mrzbn Dec 28 '22 at 19:07

3 Answers3

4

All you need to calculate the Grid's height beforehand.

    @Composable
    fun HomeItem1() {
        Surface(modifier = Modifier.nestedScroll(rememberViewInteropNestedScrollConnection())) {
        LazyColumn {
            //other contents
            item {
                LazyHorizontalGrid(
                    modifier = Modifier.height(176.dp), // itemHeight * rowCount + verticalSpacing * (rowCount - 1)
                    rows = GridCells.Fixed(3),
                    horizontalArrangement = Arrangement.spacedBy(16.dp),
                    verticalArrangement = Arrangement.spacedBy(16.dp)
                ) {
                    items(arrayList.size) {
                        Text(arrayList[it], modifier = Modifier.height(48.dp))
                    }
                }
            }
        }
    }
}
mobimaks
  • 41
  • 1
  • 5
  • 1
    What if I want the height in terms of MaxFillParentHeight()? – Mayur Jun 22 '22 at 17:17
  • 1
    There's a problem with this code. Let's say user increased the text size in his phone and the 48.dp set as height to Text() won't be sufficient and causes the text to be clipped – Syed Zeeshan Jul 20 '23 at 10:15
2

LazyList exceptions with dimensions occur when you try to measure respective dimension with Constraints.Infinity. I explained about in this answer how Constraints are applied based on modifiers, Modifier.scroll(). Also, using a scrollable parent Composable like LazyColumn creates Constraints with infinite height, LazyRow creates Constraints with infinite width.

You can use Modifier.heightIn(maxHeight) with maxHeight which means this LazyColumn can be measured or assigned total height of parent at most if sum of content height is bigger than rest of the height that is not used by other items.

You can use Modifier.heightIn(maxHeight) or Modifier.layout{} and constrain max height to screen height or selected height in px. Constraints return dimensions in px that is to be used with layout function. Getting Constrains height from parent by wrapping it with BoxWithConstraints returns non-infinite height

@Composable
private fun Sample() {

    val arrayList = remember {
        mutableStateListOf<String>().apply {
            repeat(40) {
                add("Item $it")
            }
        }
    }


    Column(
        modifier = Modifier
            .border(4.dp, Color.Green)
            .fillMaxSize()

    ) {

        BoxWithConstraints {

            val parentConstraints = this.constraints
            val maxHeight = this.maxHeight

            LazyColumn {
                //other contents
                item {
                    Image(
                        modifier = Modifier
                            .height(200.dp)
                            .fillMaxWidth(),
                        painter = painterResource(id = R.drawable.landscape2),
                        contentDescription = null,
                        contentScale = ContentScale.FillBounds
                    )
                }
                item {

                    LazyHorizontalGrid(
                        modifier = Modifier
                            .border(2.dp, Color.Red)
                           // Alternative 1
                           // .heightIn(maxHeight)
                           // Alternative 2
                            .layout { measurable, constraints ->
                                val placeable = measurable.measure(
                                    constraints.copy(maxHeight = parentConstraints.maxHeight)
                                )

                                layout(placeable.width, placeable.height) {
                                    placeable.placeRelative(0, 0)
                                }
                            },
                        rows = GridCells.Fixed(3),
                        horizontalArrangement = Arrangement.spacedBy(16.dp),
                        verticalArrangement = Arrangement.spacedBy(16.dp)
                    ) {
                        items(arrayList.size) {
                            Box(
                                modifier = Modifier
                                    .shadow(2.dp, RoundedCornerShape(8.dp))
                                    .background(Color.White)
                                    .aspectRatio(1f)
                                    .padding(2.dp)
                            ) {
                                Text(arrayList[it])
                            }
                        }
                    }
                }
            }
        }
    }
}

The example above creates a LazyHorizontalGrid with full height of screen, i added an Image as another element to show that grid covers parent height.

The important thing here is how you set maxHeight for Constraints in

constraints.copy(maxHeight = parentConstraints.maxHeight)

If you want grid to match item size then you should measure size of an item

using rowCount * itemHeight + (rowCount-1)* verticalSpacingInPx.

constraints.copy(maxHeight = 600f) for instance.

If you don't know the height of the item you should use Modifier.onSizeChanged or as in this answer SubcomposeLayout or Layout

How to get exact size without recomposition?

And using Text as dependent item we can do it as

@Composable
private fun Sample2() {

    val arrayList = remember {
        mutableStateListOf<String>().apply {
            repeat(40) {
                add("Item $it")
            }
        }
    }

    val mainComposable = @Composable {
        Box(
            modifier = Modifier
                .shadow(2.dp, RoundedCornerShape(8.dp))
                .background(Color.White)
                .size(80.dp)
                .padding(2.dp)
        ) {
            Text(arrayList[0])
        }
    }

    Column(
        modifier = Modifier
            .border(4.dp, Color.Green)
            .fillMaxSize()

    ) {

        DimensionSubcomposeLayout(
            rowCount = 3,
            verticalSpacing = 16.dp,
            mainContent = {
                mainComposable()
            }
        ) { size: Dp ->
            LazyColumn {

                //other contents
                item {
                    Image(
                        modifier = Modifier
                            .height(200.dp)
                            .fillMaxWidth(),
                        painter = painterResource(id = R.drawable.landscape2),
                        contentDescription = null,
                        contentScale = ContentScale.FillBounds
                    )
                }

                item {
                    LazyHorizontalGrid(
                        modifier = Modifier
                            .height(size)
                            .border(2.dp, Color.Red),
                        rows = GridCells.Fixed(3),
                        horizontalArrangement = Arrangement.spacedBy(16.dp),
                        verticalArrangement = Arrangement.spacedBy(16.dp)
                    ) {
                        items(arrayList.size) {
                            Box(
                                modifier = Modifier
                                    .shadow(2.dp, RoundedCornerShape(8.dp))
                                    .background(Color.White)
                                    .size(80.dp)
                                    .padding(2.dp)
                            ) {
                                Text(arrayList[it])
                            }
                        }
                    }
                }
            }
        }
    }
}

SubComposeLayout that uses Text height and spacing to calculate height for LazyHorizontal Grid

@Composable
fun DimensionSubcomposeLayout(
    modifier: Modifier = Modifier,
    rowCount: Int,
    verticalSpacing: Dp = 0.dp,
    mainContent: @Composable () -> Unit,
    dependentContent: @Composable (Dp) -> Unit
) {

    val density = LocalDensity.current
    val verticalSpacingPx = density.run { verticalSpacing.toPx() }

    SubcomposeLayout(
        modifier = modifier
    ) { constraints: Constraints ->

        // Subcompose(compose only a section) main content and get Placeable
        val mainPlaceable = subcompose(SlotsEnum.Main, mainContent)
            .map {
                it.measure(constraints)
            }
            .first()


        // Get max width and height of main component

        val maxHeight = mainPlaceable.height * rowCount + (rowCount - 1) * verticalSpacingPx

        val dpSize = with(density.density) {
            maxHeight.toDp()
        }

        val dependentPlaceable = subcompose(SlotsEnum.Dependent) {
            dependentContent(dpSize)
        }
            .map { measurable: Measurable ->
                measurable.measure(constraints)
            }
            .first()

        layout(dependentPlaceable.width, dependentPlaceable.height) {
            dependentPlaceable.placeRelative(0, 0)
        }
    }
}
Thracian
  • 43,021
  • 16
  • 133
  • 222
  • I cannot calculate height of items in LazyHorizontalGrid. And when I set a large max height (e.g. 2000.dp), LazyHorizontalGrid fills all that height. Still I don't understand, logically, what is the difference between 1 row (LazyRow) and multiple rows (LazyHorizontalGrid). All solutions you mentioned was workaround, but I think this should come as feature. – mrzbn Jan 01 '23 at 06:34
  • Worked like a charm. Thanks. – Kedar Tendolkar Aug 31 '23 at 09:55
  • They deliberately throw exception when content in LazyList or Column with vertical scroll. These scrolls return Constraints.Infinity and you need to set maxHeight of Constraints to something finite either by calculating exact height or limiting it some bounds with an arbitrary number. Let's say you give 200.dp and your actual content height is 120.dp then your grid height is measured as 120.dp. If you think grid height will be 200.dp you can limit measurement upper bound to something big so it can be 220.dp or 300.dp – Thracian Aug 31 '23 at 10:07
0

Just set a fixed height or set a max height to the LazyHorizontalGrid.

    @Composable
    fun HomeItem1() {
    Surface(modifier = Modifier.nestedScroll(rememberViewInteropNestedScrollConnection())) {
        LazyColumn {
            //other contents
            item {
                LazyHorizontalGrid(
                    modifier = Modifier.heightIn(max=200.dp),
                    rows = GridCells.Fixed(3),
                    horizontalArrangement = Arrangement.spacedBy(16.dp),
                    verticalArrangement = Arrangement.spacedBy(16.dp)
                ) {
                    items(arrayList.size) {
                        Text(arrayList[it])
                    }
                }
            }
        }
    }
}

If the height inscreases more than 200.dp, then the Grid will start nested scrolling. If you really want to make sure that you don't want the nested scrolling, an "ugly" work around would be to set a really large max height.

Syed Zeeshan
  • 490
  • 1
  • 7
  • 13