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.