https://kotlinlang.org logo
#compose
Title
# compose
k

Kari Kähkönen

02/08/2021, 9:10 PM
I'm implementing a screen that contains a LazyColumn with items that have a checkbox in each of them. I want to have all the checkboxes disabled until the user makes an action (selects an option from separate dropdown menu). The problem is that when I update a checkbox state to enabled=true, the checkbox doesn't update until it is scrolled away from the screen and then back again. I made a simplified version (see thread below) with a single checkbox that is initially disabled and a button that enables it. Pressing the button, however, does not update the checkbox (it is not recomposed?). If I set the checkbox enabled initially and change the button to update the checked state, it works. Is there something special about the enabled parameter that I'm not getting or is there a bug?
Copy code
data class State(
    val checked: Boolean,
    val enabled: Boolean
)

@Composable
fun CheckboxTest() {
    var state by remember { mutableStateOf(State(checked = false, enabled = false)) }

    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedButton(onClick = { state = state.copy(enabled = true) }) {
            Text("Enable")
        }

        Checkbox(
            checked = state.checked,
            enabled = state.enabled,
            onCheckedChange = { state = state.copy(checked = !state.checked) },
            modifier = Modifier.padding(end = 16.dp)
        )
    }
}
j

jim

02/08/2021, 9:47 PM
Your state object does not have observable fields, so Compose has no way of knowing (being notified) when it has been changed. You can model your state object like this:
Copy code
class State() {
    var checked: Boolean by mutableStateOf(false)
    var enabled: Boolean by mutableStateOf(false)
}
k

Kari Kähkönen

02/08/2021, 10:03 PM
But if I set initial value of enabled to true (in the first line of the composable function) and change the on click of outlined button to set checked to true (instead of updating enabled), the checkbox composabe will update just fine. So the code works when changing checked but not when changing enabled.
r

Rick Regan

02/09/2021, 5:03 AM
If you add this to your CheckBox
Copy code
colors = CheckboxDefaults.colors(
        checkedColor = Color.Green,
        uncheckedColor = Color.Blue,
        checkmarkColor = Color.Black,
        disabledColor = Color.Red
)
and then click the enable button and then the checkbox, you'll get a black check in a red outlined box. If you change the line
enabled = state.enabled
to
enabled = true
(so no need to click enable button) you'll get a black check in a solid green box when you click the checkbox. I have no idea what that means.
k

Kari Kähkönen

02/09/2021, 6:54 AM
There is definitely something weird going on with the checkbox. I think the state works fine in my example but the data class is remnant from the more complex version so I created a new version with just two mutable state variables:
Copy code
@Composable
fun CheckboxTest() {
    var checked by remember { mutableStateOf(false) }
    var enabled by remember { mutableStateOf(false) }

    Column(
        modifier = Modifier.padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {

        Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
            Button(onClick = { enabled = true }) {
                Text("Enable")
            }

            Button(onClick = { enabled = false }) {
                Text("Disable")
            }
        }

        Checkbox(
            checked = checked,
            enabled = enabled,
            onCheckedChange = { checked = !checked },
            modifier = Modifier.padding(end = 16.dp)
        )

        Checkbox(
            checked = checked,
            enabled = enabled,
            onCheckedChange = { checked = !checked },
            modifier = Modifier.padding(end = 16.dp),
            colors = CheckboxDefaults.colors(
                checkedColor = Color.Green,
                uncheckedColor = Color.Blue,
                checkmarkColor = Color.Black,
                disabledColor = Color.Red
            )
        )

        Text(text = "Enabled $enabled, Checked: $checked")
    }
}
Here is also a screenshot after pressing enable button and clicking one of the checkboxes (which have the same state so they both should be checked). I'll file a bug after work later today.
r

Rick Regan

02/09/2021, 12:33 PM
If you add colors to the first checkbox as well you will see they will both be checked...
k

Kari Kähkönen

02/09/2021, 12:51 PM
Yeah, it seems that the checkboxes are working (i.e., are clickable when enabled) but they are rendered incorrectly or something. If I set in the code the initial state of the checkboxes to be enabled and then check one of them, they both show a checkmark and the second checkbox has green color. So the state is the same as in the picture but the checkboxes look completely different.
r

Rick Regan

02/09/2021, 1:03 PM
Yes. Very strange. Red (disabled color) shouldn't be coming into play when the box is enabled. Good luck describing the bug :)
k

Kari Kähkönen

02/09/2021, 4:42 PM
a

Alex Bieliaiev

02/09/2021, 11:41 PM
Very interesting case indeed. The problem seems to be somewhere in the boxColor and borderColor functions, which are called from the CheckboxImpl and return either mutable state or AnimatedState. Upon first recomposition with a different value of enabled argument everything just breaks.
2 Views