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.