0

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.

J_Stan
  • 473
  • 1
  • 5
  • 14
  • I would keep the state of the 3 checkboxes in a ViewModel, would make things a lot simpler. – Tom Berghuis Jul 13 '23 at 09:14
  • If I understood correctly, checkboxes = newCheckboxesState would be working. If yes, because recomposition would be triggered on assignment operation, not when same object reference is updated. Let me know if it still doesn't work. – prateek Jul 13 '23 at 10:03
  • @prateek With a mutable state list, recomposition occurs when the list is updated. I confirmed this through my final test (last paragraph of original post). – J_Stan Jul 13 '23 at 13:55
  • @TomBerghuis - Without going into much detail, that is not an option in my case. I need these to work as independent composeables – J_Stan Jul 13 '23 at 13:56

1 Answers1

0

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.

J_Stan
  • 473
  • 1
  • 5
  • 14