Hey folks, looking for feedback on a custom Compos...
# compose
v
Hey folks, looking for feedback on a custom Compose table layout implementation using
SubcomposeLayout
The goal is to support: • dynamic column sizing • wrap-content + weighted columns • consistent widths across rows • flexible rendering based on available space The current approach uses a multi-pass measurement flow: 1. measure wrap-content columns 2. Cache resolved widths 3. Distribute remaining width across weighted columns 4. perform final measurement/layout pass Would love feedback on and open to any suggestions or improvements 🧵.
Copy code
@Composable
fun AdaptiveChecklistRowLayout(
    layoutSpec: ChecklistLayoutSpec,
    measuredWidths: SnapshotStateMap<ChecklistFieldKind, Int>,
    modifier: Modifier = Modifier,
    cell: @Composable (ChecklistFieldSpec) -> Unit,
) {
    SubcomposeLayout(
        modifier = modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp),
    ) { constraints ->

        val columnSpacingPx = 24.dp.roundToPx()
        val fields = layoutSpec.visibleFields

        fields.forEach { field ->
            val widthRule = field.widthRule

            if (widthRule is FieldWidthRule.ContentBased) {
                val minimumWidthPx = widthRule.minimumPx.dp.roundToPx()

                val measuredWidth = subcompose("measure_${field.fieldKind}") {
                    cell(field)
                }.first().measure(Constraints()).width

                val resolvedWidth = maxOf(measuredWidth, minimumWidthPx)
                val previousWidth = measuredWidths[field.fieldKind] ?: 0

                if (resolvedWidth > previousWidth) {
                    measuredWidths[field.fieldKind] = resolvedWidth
                }
            }
        }

        val spacingWidth = ((fields.size - 1).coerceAtLeast(0)) * columnSpacingPx

        var contentBasedWidth = 0
        var totalFlexibleRatio = 0f

        fields.forEach { field ->
            when (val widthRule = field.widthRule) {
                is FieldWidthRule.ContentBased -> {
                    contentBasedWidth += measuredWidths[field.fieldKind]
                        ?: widthRule.minimumPx.dp.roundToPx()
                }

                is FieldWidthRule.Flexible -> {
                    totalFlexibleRatio += widthRule.ratio
                }
            }
        }

        val flexibleAvailableWidth =
            (constraints.maxWidth - contentBasedWidth - spacingWidth).coerceAtLeast(0)

        val safeRatioTotal = totalFlexibleRatio.coerceAtLeast(1f)

        val cells = fields.map { field ->
            val fieldWidth = when (val widthRule = field.widthRule) {
                is FieldWidthRule.ContentBased -> {
                    measuredWidths[field.fieldKind]
                        ?: widthRule.minimumPx.dp.roundToPx()
                }

                is FieldWidthRule.Flexible -> {
                    ((widthRule.ratio / safeRatioTotal) * flexibleAvailableWidth).toInt()
                }
            }.coerceAtLeast(0)

            subcompose("layout_${field.fieldKind}") {
                cell(field)
            }.first().measure(
                Constraints.fixedWidth(fieldWidth),
            )
        }

        val rowHeight = cells.maxOfOrNull { it.height } ?: 0

        layout(
            width = constraints.maxWidth,
            height = rowHeight,
        ) {
            var currentX = 0

            cells.forEach { cellPlaceable ->
                cellPlaceable.placeRelative(
                    x = currentX,
                    y = 0,
                )

                currentX += cellPlaceable.width + columnSpacingPx
            }
        }
    }
}
o
Did you check whether you could use new Grid component instead of implementing a custom layout? It is quite flexible and I think with intrinsic sizing and "fr" units you could achieve the required result. https://developer.android.com/develop/ui/compose/layouts/adaptive/grid/container-properties#grid-definition
v
Hey, thanks for the suggestion! Before I try it out, I wanted to share the problem we are trying to solve and check if your suggestion covers it. We have a list of rows that each render on their own, and they don't know about each other. The problem is that the same column in different rows can end up being different widths, which makes the whole thing look misaligned and messy.
What we need is for all rows to always have the same column widths, so everything lines up neatly like a proper table, no matter what content is inside each row. I haven't tried your suggestion yet, does it handle this case where all rows need to agree on the same column widths? If it does, I'll definitely give it a try!
o
I see, yes, as far as I understand it should be possible.
🙏 1
a
Generally speaking, using
SubcomposeLayout
to build a layout is one of the more expensive ways to do it - it'll work, but it requires deferring all of the composition work for the children until measurement. The new
Grid
should be exactly what you're looking for here