Search code examples
android-jetpack-composeandroid-checkboxandroid-jetpack-compose-lazy-column

CheckBox State is not changing in jetpack compose LazyColumn items()


  • I have a LazyColumn in which items() exist that holds 5 checkboxes, and the state of these can be controlled by a single parent variable as well as with a specific variable. The code is below:
 val areAllChecked = rememberSaveable {
                mutableStateOf(false)
            }
            MyApplicationTheme {
                LazyColumn(modifier = Modifier.fillMaxSize()) {
                    item {
                        IconButton(onClick = { areAllChecked.value = false }) {
                            Icon(imageVector = Icons.Default.Clear, contentDescription = null)
                        }
                    }
                    item {
                        Row(verticalAlignment = Alignment.CenterVertically){
                            Text(text = "Parent")
                            Checkbox(checked = areAllChecked.value, onCheckedChange = {
                                areAllChecked.value = it
                            })
                        }
                    }
                    items(5) {
                        val isChecked = rememberSaveable(areAllChecked.value) {
                            mutableStateOf(areAllChecked.value)
                        }
                        Row(verticalAlignment = Alignment.CenterVertically){
                            Text(text = it.toString())
                            Checkbox(checked = isChecked.value, onCheckedChange = {
                                isChecked.value = it
                            })
                        }
                    }
                }
            }
  • Here, areAllChecked is a parent variable for controlling the state, and isChecked is a local variable for checkboxes that changes when areAllChecked gets changed as expected.

  • But when I press the icon button, all of the checkboxes' states will be false only if areAllChecked is true. If it's not true, the local variable, which is isChecked, remain unchanged when they should also be set to false, as the IconButton onClick triggers the areAllChecked to false.

  • I've added the GIF here to show what I'm trying to solve.

How can i fix this?

Thank you.


Solution

  • You need to wrap your item into the Data Class to manage the state of each item easily.

    Here is the full example

    Why use LaunchedEffect because we don't want to load it every time. We want to change only when the CheckBox of Parent changes.

    Whenever changes are made in the single item we loop through and change its value and assign a new list to current.

    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.Row
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.padding
    import androidx.compose.foundation.lazy.LazyColumn
    import androidx.compose.foundation.lazy.items
    import androidx.compose.material.icons.Icons
    import androidx.compose.material.icons.filled.Clear
    import androidx.compose.material3.Checkbox
    import androidx.compose.material3.ExperimentalMaterial3Api
    import androidx.compose.material3.Icon
    import androidx.compose.material3.IconButton
    import androidx.compose.material3.Scaffold
    import androidx.compose.material3.Text
    import androidx.compose.material3.TopAppBar
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.LaunchedEffect
    import androidx.compose.runtime.getValue
    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.remember
    import androidx.compose.runtime.saveable.rememberSaveable
    import androidx.compose.runtime.setValue
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    
    
    data class CheckedItem(
        val text: String, val isSelected: Boolean = false
    )
    
    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun Stack021(modifier: Modifier = Modifier) {
    
        val list = listOf(
            CheckedItem("A", false),
            CheckedItem("B", false),
            CheckedItem("C", false),
            CheckedItem("D", false),
            CheckedItem("E", false),
            CheckedItem("F", false),
            CheckedItem("G", false)
        )
    
        var useList by remember {
            mutableStateOf(list)
        }
    
    
        val isAllAreCheck = rememberSaveable {
            mutableStateOf(false)
        }
        
        LaunchedEffect(isAllAreCheck.value) {
            useList = if (isAllAreCheck.value) {
                useList.mapIndexed { j, item ->
                    item.copy(isSelected = true)
                }
            } else {
                useList.mapIndexed { j, item ->
                    item.copy(isSelected = false)
                }
            }
        }
    
        Scaffold(topBar = {
            TopAppBar(title = {
                Text(text = "Multi Select Demo")
            })
        }) { padding ->
    
    
            Column(
                modifier = Modifier
                    .padding(padding)
                    .fillMaxSize()
            ) {
    
                IconButton(onClick = {
                    println("All Are Unchecked")
                    useList = useList.mapIndexed { j, item ->
                        item.copy(isSelected = false)
                    }
                }) {
                    Icon(imageVector = Icons.Default.Clear, contentDescription = null)
                }
    
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Text(text = "Parent")
                    Checkbox(
                        checked = isAllAreCheck.value,
                        onCheckedChange = {
                            isAllAreCheck.value = it
                        }
                    )
                }
    
    
                LazyColumn(
                    modifier = Modifier
                        .fillMaxSize()
                        .weight(1f)
                ) {
                    items(useList.size) { i ->
    
                        Row(verticalAlignment = Alignment.CenterVertically) {
                            Text(text = useList[i].text)
                            Checkbox(checked = useList[i].isSelected, onCheckedChange = {
                                useList = useList.mapIndexed { j, item ->
                                    if (i == j) {
                                        item.copy(isSelected = it)
                                    } else item
                                }
                            })
                        }
    
                    }
                }
            }
    
        }
    }