Reputation: 3936
In the ViewModel, I have a MutableLiveData whose generic type is a list of custom objects. The object has a selected
field which can be changed from the UI. Now, I am using a RecyclerView to populate the UI and DiffUtil.Callback to dispatch the right changes.
When the checkbox in the ViewHolder is checked, a callback in the Fragment (containing the RecyclerView) get notified. And then a method in the ViewHolder is called to change the selected
value of the item and then subsequently calls setValue
.
Fragment
private fun observeViewModel() {
viewModel.data.observe(viewLifecycleOwner, Observer {
adapter.updateItems(items)
this.items = items
})
}
override fun onSelectionChange(position: Int) {
viewModel.reverseSelection(position)
}
ViewModel
fun reverseSelection(index: Int) {
data.value?.let {
if (it.size > index) {
it[index].selected = !it[index].selected
data.value = it
}
}
}
RecyclerView Adapter
fun updateItems(items: List<T>) {
val diffCallback = BaseDiffCallback(this.items, items)
val diffResult = DiffUtil.calculateDiff(diffCallback, false)
this.items = items
diffResult.dispatchUpdatesTo(this)
}
The issue is that DiffUtil is not detecting any changes because it appears the items
field in the Fragment and the data value in the ViewHolder are pointing to the same object in memory. With log statements, I noticed that the items in the fragment already reflects the changes before explicitly calling setValue
on the data in the ViewHolder and before triggering the observer.
Please, how can I change the selection
state without the items in the Fragment reflecting the state until I call setValue
in the ViewHolder?
I really appreciate the kind users who took their time to provide solutions to my question. However because I didn't explain my question well enough, the proposed solutions didn't work for me.
One of the critical details I missed out is that I am using databinding and the checked state of the model determines the state of the other views in the ViewHolder
. Additionally, I made a typo earlier, reverseSelection
is not in the ViewHolder
but ViewModel
. Finally, I am using both the Adapter
and the ViewHolder
for other data models.
So, it's very important that the selected
variable reside in the data class otherwise, there'll be a performance overhead when looking through the elements in the HashMap or Set; checking if the item at position
is selected or not. The data set can be quite large. Additionally, because I am using the RecyclerView.Adapter
and ViewHolder
for other data models, I feel using a HashMap or a Set is not the best solution.
For now, I am no longer depending on the ViewHolder to handle propagation of the changed data model. The new updates are thus:
ViewModel
fun reverseSelection(index: Int): Boolean {
return data.value?.let {
if (it.size > index) {
it[index].selected = !it[index].selected
true
} else false
} ?: false
}
Fragment
override fun onSelectionChange(position: Int) {
if (viewModel.reverseSelection(position)) {
adapter.notifyItemChanged(position, 0)
}
}
I don't know if this is the best solution but I will make do with it for now.
Upvotes: 1
Views: 2036
Reputation: 1987
Create a set that takes in the position of the selected items. When the user select an item from the recycerview, add the item to the hashset if it doesn't exist in the set, however if it does, remove the item from the set. Then notify the adapter to update the view on the screen. Now pass the hashset as an argument to your onSelectionChange()
listener method in your click listener.
fun reverseSelection(index: Int) {
data.value?.let {
if (set.contains(it)) {
//Uncheck the checkbox
set.remove(it)
return
}
set.add(it)
//Check the checkbox
//Call the interface/listener method here and pass the hashset as a parameter to it. The set consist of the seletected positions.
//Notify the adapter by calling notifyAdapterSetChanged()
}
}
You don't need any logic in your viewmodel for reversing the selected item.
Upvotes: 1
Reputation: 155
DiffUtil is a class that can calculate the difference between two different lists and output a list of update operations that converts the first list into the second one. So, if you change in perform a data change onto same list, diffUtil class won't get called.
The change you are observing in MutableLivedata, is because you're changing your model list on selecting the item. So, whenever any values gets updated which you're observing, mutablelivedate would call onChange method to notify.
To keep a note of selectable view on change, you could create a variable for it in your model class and can access whole object later on. Or, you can watch the whole list, so whenever a list would gets modified, you can observe a change in onChange() method.
Upvotes: 1
Reputation: 8831
In the adapter, i would create a hash map that will store the indices of selected items instead of directly modifying the items. This will make sure that the objects are not modified but you also store the the selected indices.
Upvotes: 1