Home > Enterprise >  Jetpack Compose:LazyVerticalGrid within Column with verticalScroll modifier throws java.lang.Illegal
Jetpack Compose:LazyVerticalGrid within Column with verticalScroll modifier throws java.lang.Illegal

Time:07-23

I am trying to achieve this:

enter image description here

This section is inside my ProductDetailsScreen composable. Here is the code:


@Composable
fun ProductDetailsScreen(
    modifier: Modifier = Modifier
) {

    val scrollState = rememberScrollState()

    Column(modifier = Modifier
                    .padding(horizontal = dimensionResource(id = R.dimen.dimen_24))
                    .verticalScroll(scrollState) // this makes the screen scrollable 
            ) {             
                //images
                ProductImages(
                    images = product?.productImage
                )

               //other stuff...

            }
     }

Here is also my ProductImages composable:


@Composable
fun ProductImages(
    modifier: Modifier = Modifier,
    images: List<String>? = null,
) {
    if (!images.isNullOrEmpty()) {

        Column(
            modifier = modifier,
            verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.dimen_16))
        ) {

            //title
            ProductSectionTitle(
                title = String.format(
                    stringResource(id = R.string.media_section_title),
                    images.size
                )
            )
            //images
            Row() {
                ProductImage(modifier = Modifier.size(160.dp), image = images[0])
                LazyVerticalGrid(
                    columns = GridCells.Fixed(2)
                ) {
                    items(images.subList(1, 5)) { image ->
                        ProductImage(modifier = Modifier.size(75.dp), image = image)
                    }
                }
            }
        }
    }
}

As you can see in the layout I have 5 images. To achieve this layout I put the components within a Row. So The first image which is the biggest one is the first item of the Row as you can see from this code:

            //images
            Row() {
             ProductImage(modifier = Modifier.size(160.dp), image = images[0])
            }

And I put the other images into the LazyVerticalGrid cuz it divides images into the cells and I don't need to handle the calculation process by myself :


   LazyVerticalGrid(
                    columns = GridCells.Fixed(2)
                ) {
                    //since I already load the first image, I sublist the original one and take only 4 images
                    items(images.subList(1, 5)) { image ->
                        ProductImage(modifier = Modifier.size(75.dp), image = image)
                    }
                }

But since it is in the Column that has the .verticalScroll() modifier it throws:

java.lang.IllegalStateException:Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There are could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container.

I don't want to give a fixed height to the LazyVerticalGrid which fixes the issue. I want to make it wrap its content. Is there any other way to achieve this layout or how can I prevent this error?

CodePudding user response:

Setting a Modifier with fixed height for LazyVerticalGrid would solve it.

enter image description here

I made this example with LazyColumn but Column with vertical scrollstrong text also works when LazyVerticalGrid has 160.dp height

@Composable
private fun CustomGrid() {


    LazyColumn(
        contentPadding = PaddingValues(8.dp),
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.spacedBy(10.dp)
    ) {

        item {
            Text("Title", fontSize = 20.sp)
        }

        item {
            Row {
                Image(
                    painter = painterResource(id = images.first()),
                    modifier = Modifier
                        .size(160.dp)
                        .clip(RoundedCornerShape(10.dp)),
                    contentScale = ContentScale.FillBounds,
                    contentDescription = null
                )
                Spacer(modifier = Modifier.width(10.dp))
                LazyVerticalGrid(
                    verticalArrangement = Arrangement.spacedBy(10.dp),
                    horizontalArrangement = Arrangement.spacedBy(10.dp),
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(160.dp),
                    columns = GridCells.Fixed(2)
                ) {
                    items(items = images.subList(1, 5)) { image ->
                        Image(
                            modifier = Modifier
                                .size(75.dp)
                                .clip(RoundedCornerShape(10.dp)),
                            painter = painterResource(id = image),
                            contentScale = ContentScale.FillBounds,
                            contentDescription = null
                        )
                    }
                }
            }
        }

        item {
            Text("Another Title", fontSize = 20.sp)
        }
    }
}

CodePudding user response:

This applies only to cases when nesting scrollable children without a predefined size inside another same direction scrollable parent. For example, trying to nest a child LazyColumn without a fixed height inside a vertically scrollable Column parent:

// Throws IllegalStateException
Column(
    modifier = Modifier.verticalScroll(state)
) {
    LazyVerticalGrid {
        // ...
    }
}

Instead, the same result can be achieved by wrapping all of your composables inside one parent LazyColumn and using its DSL to pass in different types of content. This enables emitting single items, as well as multiple list items, all in one place:

LazyVerticalGrid {
    item {
        Header()
    }
    items(data) { item ->
        Item(item)
    }
    item {
        Footer()
    }
}

reference: https://developer.android.com/jetpack/compose/lists#avoid-nesting-scrollable

CodePudding user response:

What you can do is borrow and adapt the sample Grid.kt designed by the Jetpack Compose team. You can then use it everywhere in your project within LazyColumns. The code is Open Source:

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.jetsnack.ui.components
    
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.layout.Layout
    
    /**
     * A simple grid which lays elements out vertically in evenly sized [columns].
     */
    @Composable
    fun VerticalGrid(
        modifier: Modifier = Modifier,
        columns: Int = 2,
        content: @Composable () -> Unit
    ) {
        Layout(
            content = content,
            modifier = modifier
        ) { measurables, constraints ->
            val itemWidth = constraints.maxWidth / columns
            // Keep given height constraints, but set an exact width
            val itemConstraints = constraints.copy(
                minWidth = itemWidth,
                maxWidth = itemWidth
            )
            // Measure each item with these constraints
            val placeables = measurables.map { it.measure(itemConstraints) }
            // Track each columns height so we can calculate the overall height
            val columnHeights = Array(columns) { 0 }
            placeables.forEachIndexed { index, placeable ->
                val column = index % columns
                columnHeights[column]  = placeable.height
            }
            val height = (columnHeights.maxOrNull() ?: constraints.minHeight)
                .coerceAtMost(constraints.maxHeight)
            layout(
                width = constraints.maxWidth,
                height = height
            ) {
                // Track the Y co-ord per column we have placed up to
                val columnY = Array(columns) { 0 }
                placeables.forEachIndexed { index, placeable ->
                    val column = index % columns
                    placeable.placeRelative(
                        x = column * itemWidth,
                        y = columnY[column]
                    )
                    columnY[column]  = placeable.height
                }
            }
        }
    }

You can use it like this:

VerticalGrid(
                columns = 2
            ) {
                (images.subList(1, 5)).forEach { image ->
                    ProductImage(modifier = Modifier.size(75.dp), image = image)
                }
            }
  • Related