Home > Software engineering >  Jetpack Compose Checkbox Not Showing Changed State
Jetpack Compose Checkbox Not Showing Changed State

Time:11-20

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
)

CodePudding user response:

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.

CodePudding user response:

This is clearly not how Compose would work. Read more in the documentation, but for now, the fix would be to replace your existing VM implementation with this

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

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

You cannot wrap anything inside a call to mutableStateOf and expect it to trigger recompositions. See this question: Create Custom MutableState<T> Holders

EDIT: Based on the comment by @Philip below to your post, it came to my attention that you are also wrongly implementing the LazyColumn. You must place every element in a separate item for the column to effectively cache and discard elements per need.

@Composable
fun SpecialtyList(
    viewModel: SpecialtyListViewModel = hiltViewModel(),
    navController: NavController
) {
    LazyColumn(modifier = Modifier.fillMaxSize()) {    
         viewModel.specialtyList.value.forEach { specialty ->
             item{
                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)
                    )
                }
            }
        }
    }
}

This is a minor swap. You can also leverage other ways of implementing this, for example, using the items block.

  • Related