Currently in Jetpack Compose, this code throws an
IllegalStateException because you cannot nest two vertically scrolling
Composables:
This is not entirely true, what it says you can't pass Constraints.Infinity to another scrollable Composable. Composables with scroll as LazyLists also rely on scroll modifier are not allowed to measure themselves with maxWidth/Height of infinity. If you change maxHeight to something finite you won't have the issue. Constraints mean a range to measure Composables before placing them. If you pass a 0-finite dimension it will be measured in dimension.
You don't have give fix height either if your content in vertical grid is smaller than parent height- sum of other content height.
Passing it with Modifier.heighIn(max) you constrain height measurement of grid to 0 and between parent.
@Preview
@Composable
private fun LazyTest() {
BoxWithConstraints {
val parentHeight = maxHeight
LazyColumn {
item {
Text(text = "My LazyColumn Title")
}
item {
LazyVerticalGrid(
modifier = Modifier.heightIn(max = parentHeight),
columns = GridCells.Fixed(4)
) {
items(10) {
Box(
modifier = Modifier
.size(50.dp)
.padding(5.dp)
.background(Color.Gray)
)
}
}
}
}
}
}
If in your case other if you wish to allow Grid to cover rest of the LazyColumn height you can measure height of other elements and use Modifier.heigh(parent height - sum of height of other content), this is actually pretty easy with a custom Layout
.
And if you are curious why this exception occurs it's in scroll code because they do a check before setting composable with scroll modifier as.
private class ScrollingLayoutNode(
var scrollerState: ScrollState,
var isReversed: Boolean,
var isVertical: Boolean
) : LayoutModifierNode, Modifier.Node() {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
// And here in LazyVerticalGrid there is a check if there is Constraints.Infinity
checkScrollableContainerConstraints(
constraints,
if (isVertical) Orientation.Vertical else Orientation.Horizontal
)
// 🔥 this is how LazyColumn passes Constraints.Infinity content or child Composables
val childConstraints = constraints.copy(
maxHeight = if (isVertical) Constraints.Infinity else constraints.maxHeight,
maxWidth = if (isVertical) constraints.maxWidth else Constraints.Infinity
)
val placeable = measurable.measure(childConstraints)
val width = placeable.width.coerceAtMost(constraints.maxWidth)
val height = placeable.height.coerceAtMost(constraints.maxHeight)
val scrollHeight = placeable.height - height
val scrollWidth = placeable.width - width
val side = if (isVertical) scrollHeight else scrollWidth
// The max value must be updated before returning from the measure block so that any other
// chained RemeasurementModifiers that try to perform scrolling based on the new
// measurements inside onRemeasured are able to scroll to the new max based on the newly-
// measured size.
}
And checkScrollableContainerConstraints
function is
fun checkScrollableContainerConstraints(
constraints: Constraints,
orientation: Orientation
) {
if (orientation == Orientation.Vertical) {
check(constraints.maxHeight != Constraints.Infinity) {
"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."
}
} else {
check(constraints.maxWidth != Constraints.Infinity) {
"Horizontally scrollable component was measured with an infinity maximum width " +
"constraints, which is disallowed. One of the common reasons is nesting layouts " +
"like LazyRow and Row(Modifier.horizontalScroll()). 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 LazyRow 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."
}
}
}