kotlinandroid-jetpack-composecompose-desktopjetbrains-compose

Divider composable becomes invisible when placed inside composable with horizontalScroll modifier set. Is this a bug?


Background

I am making a desktop compose application.

I had a LazyColumn with Divider separating items. LazyColumn's width may not fit in window so I made the LazyColumn horizontally scrollable by enclosing it inside a Box with horizontalScroll() modifier set.

Now LazyColumn became horizontal scrollable as well. But strangely the Divider's separating the items disappeared.

After digging into it for a while, I figured out that Divider's became invisible only when placed inside a horizontally scrollable parent.


Minimal Reproduction

Here is a minimal reproduction of observed behavior where red Divider is clearly invisible when horizontalScroll(rememberScrollState()) modifier is set in enclosing Box.

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        Box(
            Modifier.fillMaxSize()
                .background(Color.Green)
                .horizontalScroll(rememberScrollState())
        ) {
            Divider(thickness = 10.dp, color = Color.Red)
        }
    }
}

With horizontal scroll, divider is invisible!

As it can be seen that the red Divider is invisible for above code.


Expected Output:

With verticalScroll() or no scroll modifier at all works fine as expected.

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        Box(
            Modifier.fillMaxSize()
                .background(Color.Green)
                .verticalScroll(rememberScrollState())
        ) {
            Divider(thickness = 10.dp, color = Color.Red)
        }
    }
}

As expected the divider is visible

Correct output as expected, the red Divider is clearly visible for above code.


Version info

kotlin("jvm") version "1.5.21"
id("org.jetbrains.compose") version "1.0.0-alpha3"

I'd like to know if this is a bug? or is there a way to fix this.


Solution

  • No, this is not a bug.

    Divider is used to take the full width, just like any other view with the fillMaxWidth() modifier.

    But inside horizontalScroll this modifier is ignored because it is ambiguous: horizontalScroll wraps the size of the content, so the maxWidth constraint in this area is infinite.

    From fillMaxWidth documentation:

    If the incoming maximum width is Constraints.Infinity this modifier will have no effect.

    I haven't found any documentation mentioning horizontalScroll effects Constraints, you can see maintainer's answer on the same issue, or you can test it by yourself like this:

    Box {
        BoxWithConstraints(Modifier.fillMaxSize()) {
            // output 1080 2082
            println("non scrollable max dimensions: ${constraints.maxWidth} ${constraints.maxHeight}")
        }
        BoxWithConstraints(Modifier.fillMaxSize().horizontalScroll(rememberScrollState())) {
            // output 2147483647 2082
            println("scrollable max dimensions: ${constraints.maxWidth} ${constraints.maxHeight}")
        }
    }
    

    In this case you need to specify the width explicitly, e.g:

    Box(
        Modifier
            .fillMaxSize()
            .background(Color.Green)
            .horizontalScroll(rememberScrollState())
    ) {
        var width by remember { mutableStateOf(0) }
        val density = LocalDensity.current
        val widthDp = remember(width, density) { with(density) { width.toDp() } }
        LazyColumn(
            modifier = Modifier.onSizeChanged {
                width = it.width
            }
        ) {
            items(100) {
                Text("$it".repeat(it))
                Divider(
                    thickness = 10.dp,
                    color = Color.Red,
                    modifier = Modifier.width(widthDp)
                )
            }
        }
    }
    

    Note that when the top part of your LazyColumn is smaller than the bottom one, as in my example, the width will be smaller at first (to accommodate only the visible part), but after scrolling down and back up, the width will not be restored to its original width because the Divider width modifier will not let it shrink back down. If you need to prevent this, you need to calculate the `width' based only on the visible elements, which is more complicated.