androidandroid-jetpack-composeandroid-jetpacklazycolumnandroid-jetpack-compose-list

Managing nested state for checkboxes with Jetpack Compose


I am facing a weird issue with jetpack compose where I can get a child state to manage itself, or have the parent state manage child state, but not both.

In the below example (I removed a lot of positioning/modifier fluff), you'll see that I have a Switch() component that is used to toggle the state of the individual CustomCheckbox components below. In my current implementation (below screenshot), I am able to check and uncheck the individual boxes just fine, but the toggle is not refreshing the state of each nested checkbox.

Example

@Composable
fun MyView(state: MyState) {
    val checkboxes = remember { state.checkboxes.toMutableStateList() }
    val (toggleState, updateToggleState) = remember { mutableStateOf(false) }
    Column() {
        Row() {
            Switch(
                checked = toggleState,
                onCheckedChange = {
                    val newCheckboxesState = checkboxes.map { checkbox -> checkbox.copy(checked = it) }
                    checkboxes.clear()
                    checkboxes.addAll(newCheckboxesState)
                    updateToggleState(it)
                }
            )
            Text(text = "Check all boxes")
        }
        Spacer(Modifier.height(5.dp))

        Card() {
            Column() {
                Text(text = "Check individual boxes:")
                LazyColumn() {
                    items(checkboxes) {
                        CustomCheckbox(it)
                    }
                }
            }
        }
}


@Composable
fun CustomCheckbox(
    state: MyOtherState,
) {
    val (checked, setChecked) = remember { mutableStateOf(state.checked) }

    Card() {
        Column() {
            Row() {
                Checkbox(
                    checked = checked,
                    onCheckedChange = { setChecked(it) },
                )
                Text(text = state.description)
        }
    }
}

I set up a test where I just removed the checkboxes.clear() call to see if new checkboxes would render with the correct state, and that behaved exactly as expected. I've also found that checkboxes.clear() on its own works fine. What doesn't seem to work is use of those two things in conjunction. The best way I can describe it: CustomCheckbox is "remembering" its old state on recomposition.

I've spent a full day on this and can't figure it out. Any help is appreciated.


Solution

  • Turns out the solution was really simple. Credit to this answer on another post for containing the information I needed.

    The solution was to update CustomCheckbox:

    // Instead of:
    val (checked, setChecked) = remember { mutableStateOf(state.checked) }
    
    // Do this:
    val (checked, setChecked) = remember(state.checked) { mutableStateOf(state.checked) }
    

    This is because remember has a few variations that accepts keys, and these keys are used to by remember to identify if something needs to be recomposed.