androidandroid-jetpack-composeandroid-checkbox

Jetpack Compose Checkbox Not Showing Changed State


I have a list of specialties from which the user will check. After the user is happy with their selection, I will gather the information for selected checkboxes and save those specialties in the user's profile.

However, when I tap any of the checkboxes, the state in the view model changes but checkbox is still unchecked.

Specialty List

@Composable
fun SpecialtyList(
    viewModel: SpecialtyListViewModel = hiltViewModel(),
    navController: NavController
) {
    LazyColumn(modifier = Modifier.fillMaxSize()) {
        item {
            viewModel.specialtyList.value.forEach { specialty ->
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(10.dp),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Checkbox(
                        checked = specialty.isSelected,
                        onCheckedChange = {
                            specialty.isSelected = it
                        },
                        colors = CheckboxDefaults.colors(MaterialTheme.colors.primary)
                )
                    Text(
                        text = specialty.name,
                        modifier = Modifier
                            .padding(horizontal = 10.dp)
                    )
                }
            }
        }
    }
}

Specialty View Model

@HiltViewModel
class SpecialtyListViewModel @Inject constructor() : ViewModel() {

    // HARD-CODED PROPERTIES
    val specialtyList = mutableStateOf(
        mutableListOf(
            Specialty(name = "Emergency Medicine", isSelected = false),
            Specialty(name = "Forensic Medicine", isSelected = false),
            Specialty(name = "General Practitioner", isSelected = false)
    )
}

Specialty Model

data class Specialty(
    val name: String,
    var isSelected: Boolean
)

Solution

  • In order for mutableStateOf to trigger recomposition, the container value should be updated, e.g. specialtyList = newValue.

    There's no way how it can know you've changed one of inner objects.

    Generally speaking, data class is a great tool to be used immutable in functional programming. If you won't use var inside them, you'll exclude many mistakes.

    Using mutableStateListOf if your case seems much cleaner. In your view model:

    private val _specialtyList = mutableStateListOf(
        Specialty(name = "Emergency Medicine", isSelected = false),
        Specialty(name = "Forensic Medicine", isSelected = false),
        Specialty(name = "General Practitioner", isSelected = false)
    )
    val specialtyList: List<Specialty> = _specialtyList
    
    fun setSpecialtySelectedAtIndex(index: Int, isSelected: Boolean) {
        _specialtyList[index] = _specialtyList[index].copy(isSelected = isSelected)
    }
    

    Use it from your composable like this:

    viewModel.specialtyList.value.forEachIndexed { i, specialty ->
        // ...
        onCheckedChange = {
            viewModel.setSpecialtySelectedAtIndex(i, it)
        },
    }
    

    You can find more info about state in Compose in documentation, including this youtube video which explains the basic principles.