Home > Net >  Can't represent a size of 357913941 in Constraints in Jetpack Compose
Can't represent a size of 357913941 in Constraints in Jetpack Compose

Time:11-26

I have a CustomGrid that is inside a Column. That column is inside a HorizontalPager and the HorizontalPager is inside another column where there are other elements. I want the Grid to grow in height as much as necessary, but whenever I add fillMaxHeight() or wrapContentSize() or another equivalent method, the application crashes with the error you can see in the title. Is there anything I can do to fix this error and have the Grid take up as much space as I need? I leave you the prints of my Grid, which is custom, and the respective components.

Custom Grid code:

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import kotlin.math.max

interface GridScope {
    @Stable
    fun Modifier.span(columns: Int = 1, rows: Int = 1) = this.then(
        GridData(columns, rows)
    )

    companion object : GridScope
}

private class GridData(
    val columnSpan: Int,
    val rowSpan: Int,
) : ParentDataModifier {

    override fun Density.modifyParentData(parentData: Any?): Any = this@GridData

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as GridData

        if (columnSpan != other.columnSpan) return false
        if (rowSpan != other.rowSpan) return false

        return true
    }

    override fun hashCode(): Int {
        var result = columnSpan
        result = 31 * result   rowSpan
        return result
    }
}

private val Measurable.gridData: GridData?
    get() = parentData as? GridData

private val Measurable.columnSpan: Int
    get() = gridData?.columnSpan ?: 1

private val Measurable.rowSpan: Int
    get() = gridData?.rowSpan ?: 1

data class GridInfo(
    val numChildren: Int,
    val columnSpan: Int,
    val rowSpan: Int,
)

@Composable
fun Grid(
    columns: Int,
    modifier: Modifier = Modifier,
    content: @Composable GridScope.() -> Unit,
) {
    check(columns > 0) { "Columns must be greater than 0" }
    Layout(
        content = { GridScope.content() },
        modifier = modifier,
    ) { measurables, constraints ->
        // calculate how many rows we need
        val standardGrid = GridData(1, 1)
        val spans = measurables.map { measurable -> measurable.gridData ?: standardGrid }
        val gridInfo = calculateGridInfo(spans, columns)
        val rows = gridInfo.sumOf { it.rowSpan }

        // build constraints
        val baseConstraints = Constraints.fixed(
            width = constraints.maxWidth / columns,
            height = constraints.maxHeight / rows,
        )
        val cellConstraints = measurables.map { measurable ->
            val columnSpan = measurable.columnSpan
            val rowSpan = measurable.rowSpan
            Constraints.fixed(
                width = baseConstraints.maxWidth * columnSpan,
                height = baseConstraints.maxHeight * rowSpan
            )
        }

        // measure children
        val placeables = measurables.mapIndexed { index, measurable ->
            measurable.measure(cellConstraints[index])
        }

        // place children
        layout(
            width = constraints.maxWidth,
            height = constraints.maxHeight,
        ) {
            var x = 0
            var y = 0
            var childIndex = 0
            gridInfo.forEach { info ->
                repeat(info.numChildren) {
                    val placeable = placeables[childIndex  ]
                    placeable.placeRelative(
                        x = x,
                        y = y,
                    )
                    x  = placeable.width
                }
                x = 0
                y  = info.rowSpan * baseConstraints.maxHeight
            }
        }
    }
}

private fun calculateGridInfo(
    spans: List<GridData>,
    columns: Int,
): List<GridInfo> {
    var currentColumnSpan = 0
    var currentRowSpan = 0
    var numChildren = 0
    return buildList {
        spans.forEach { span ->
            val columnSpan = span.columnSpan.coerceAtMost(columns)
            val rowSpan = span.rowSpan
            if (currentColumnSpan   columnSpan <= columns) {
                currentColumnSpan  = columnSpan
                currentRowSpan = max(currentRowSpan, rowSpan)
                  numChildren
            } else {
                add(
                    GridInfo(
                        numChildren = numChildren,
                        columnSpan = currentColumnSpan,
                        rowSpan = currentRowSpan
                    )
                )
                currentColumnSpan = columnSpan
                currentRowSpan = rowSpan
                numChildren = 1
            }
        }
        add(
            GridInfo(
                numChildren = numChildren,
                columnSpan = currentColumnSpan,
                rowSpan = currentRowSpan,
            )
        )
    }
}

Code where Grid will be inserted or other components, it is generated based on data that comes from API:

Box(
        modifier = Modifier
            .fillMaxSize()
            .background(backgroundColor.value)
    ) {

        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState())
        ) {

            // We only consume nested flings in the main-axis, allowing cross-axis flings to propagate
            // as normal
            val consumeFlingNestedScrollConnection =
                remember { ConsumeFlingNestedScrollConnection(consumeHorizontal = true) }

            Column(
                modifier = Modifier
                    .background(
                        backgroundColor.value,
                        RoundedCornerShape(topStart = Dimen30, topEnd = Dimen30)
                    )
                    .nestedScroll(connection = consumeFlingNestedScrollConnection)
                    .fillMaxWidth()
            ) {
                HorizontalPager(
                    count = size,
                    state = pagerState,
                    itemSpacing = Dimen20,
                    modifier = Modifier.padding(top = Dimen33),
                    userScrollEnabled = false
                ) { page ->
                    Column(
                        modifier = Modifier
                            // We don't any nested flings to continue in the pager, so we add a
                            // connection which consumes them.
                            .nestedScroll(connection = consumeFlingNestedScrollConnection)
                            // Constraint the content width to be <= than the width of the pager.
                            .fillParentMaxWidth()
                            .wrapContentSize()
                    ) {
                        // content (where grid could be, content is generated dinamically based on data that comes from api
                    }
                }
            }
        }
    }

How Grid is added to that layout:

Grid(
                columns = 5,
                modifier = Modifier
                    .padding(start = Dimen20, end = Dimen20, top = Dimen16)
                    .fillMaxWidth()
                    .wrapContentSize()
            ) {
// cards content
}

The crash points to the baseConstraints of the Grid code, but I can't figure out why and I can't solve the problem.

CodePudding user response:

When you use Constraints.fixed you need to have constraints that don't have maxWidth and maxHeight that are not infinite.

When you apply Modifier.verticalScroll you transform your maxHeight to Constraints.Infinity which is 2147483647. I explained in detail in this answer about constraints with vertical scroll and Constraints.

And created a sample to show why it crashes

@Composable
private fun Grid(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {

    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->

        val placeables = measurables.map { measurable: Measurable ->
            measurable.measure(
                Constraints.fixed(
                    width = constraints.maxWidth,
                    height = constraints.maxHeight
                )
            )
        }
        layout(constraints.maxWidth, constraints.maxHeight) {
            placeables.forEach {
                it.placeRelative(0, 0)
            }
        }
    }
}

demonstration why it crashes

Column(
    modifier = Modifier
        .fillMaxSize()
        .verticalScroll(rememberScrollState())
) {
    HorizontalPager(count = 3) {
        Column(
            modifier = Modifier.wrapContentSize()
        ) {
            Grid() {
                BoxWithConstraints() {
                    Text("Constraints: ${this.constraints}")
                }
            }
        }
    }
}

What you should be doing is to constrain maxHeight to parent or screen size.

One, and simple way to do is, passing it via Modifier.onSizeChanged to your Layout, but be careful using Modifier.onSizeChanged with trigger recomposition when mutableState is used to determine size of a Composable

var height by remember {
    mutableStateOf(0)
}
Box(
    modifier = Modifier
        .fillMaxSize()
        .onSizeChanged {
            if (height == 0) {
                height = it.height
            }
        }
){
    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())
    ) {
        HorizontalPager(count = 3) {
            Column(
                modifier = Modifier.wrapContentSize()
            ) {
                Grid(height = height) {
                    BoxWithConstraints {
                        Text("Constraints: ${this.constraints}")
                    }
                }
            }
        }
    }
}

And use parent height instead of infinite height due to Modifier.verticalScroll

@Composable
private fun Grid(
    modifier: Modifier = Modifier,
    height:Int,
    content: @Composable () -> Unit
) {

    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->

        val placeables = measurables.map { measurable: Measurable ->
            measurable.measure(
                Constraints.fixed(
                    width = constraints.maxWidth,
                    height = constraints.maxHeight.coerceAtMost(height)
                )
            )
        }
        layout(constraints.maxWidth, constraints.maxHeight) {
            placeables.forEach {
                it.placeRelative(0, 0)
            }
        }
    }
}

Changing your Compsable based on sample above you can achieve what you wish to create

  • Related