I cannot find a good way for selected state in ListAdapter using MVVM architecture. i am also using repository LiveData implemented for firestore by @Doug Stevenson
I am trying to avoid notifyDataSetChanged()
since causing noticeable glitch at the UI
During couple of days trying, this is my last attempt which does not work since oldList
is saved as reference so the DiffUtil.ItemCallback
not detecting any change. It seems like i need to do a deep cloning to the list but i figure there must be a better performance solution.
MainViewModel
class MainViewModel @ViewModelInject constructor(private val glistRepo: GlistRepository): ViewModel() {
private val selected = MutableLiveData<HashMap<String,Glist>>()
init {
selected.value = HashMap()
}
fun getSelected(): LiveData<HashMap<String,Glist>> {
return selected
}
fun onItemClicked(glist: Glist) {
selected.value?.set(glist.id, glist)
selected.value = selected.value //inform observers
}
....
}
MainFragment
viewModel.getSelected().observe(viewLifecycleOwner, Observer {
adapter.setSelected(it.keys.toList())
})
RepositoryCommon
For the selected state, i added a sel
object but since i do not want to add it to the database, i added to the wrapper interface
interface QueryItem<T> {
val item: T
val id: String
var sel: Boolean //The selected state
}
MainAdapter
class MainAdapter(private val clickListener: MainAdapterListener):
ListAdapter<QueryItem<Glist>, MainAdapter.ViewHolder>(asyncDifferConfig) {
private val selected = HashSet<String>()
private var oldList: List<QueryItem<Glist>>? = ArrayList()
fun setSelected(list: List<String>) {
selected.clear()
selected.addAll(list)
//Instead of using notifyDataSetChanged() i am trying to trigger the diffUtil. Not working!
submitList(oldList)
}
override fun submitList(list: List<QueryItem<Glist>>?) {
if (selected.size != 0 && oldList != null && oldList!!.isNotEmpty()) {
var id: String
for (i in oldList!!.indices) {
oldList!![i].sel = false
id = oldList!![i].id
selected.forEach { key ->
if (id == key) {
oldList!![i].sel = true
}
}
}
}
oldList = list
super.submitList(oldList)
}
QueryItemDiffCallback
open class QueryItemDiffCallback<T> : DiffUtil.ItemCallback<QueryItem<T>>() {
override fun areItemsTheSame(oldItem: QueryItem<T>, newItem: QueryItem<T>): Boolean {
return (oldItem.id == newItem.id && oldItem.sel == newItem.sel)
}
@SuppressLint("DiffUtilEquals") // equals() is OK for data classes
override fun areContentsTheSame(oldItem: QueryItem<T>, newItem: QueryItem<T>): Boolean {
return (oldItem.item == newItem.item && oldItem.sel == newItem.sel)
}
}
I am not sure that is the best practice or even if it matches MVVM but considering, it was best i could think of. If you have other ideas, please let me know and don't be hurry to downgrade
In viewmodel
i saved the selected list and the last item id changed for easy notifyItemChanged()
in MainAdapter
. In MainAdapter i inject the selected state in onBindViewHolder
using Data Binding variable.
MainViewModel
class MainViewModel @ViewModelInject constructor(private val glistRepo: GlistRepository): ViewModel() {
private val selected = MutableLiveData<HashMap<String,Glist>>()
var lastSelectedId = ""
private set
init {
selected.value = HashMap()
}
private fun toggleSelected(glist:Glist) {
lastSelectedId = glist.id
if (selected.value!!.containsKey(glist.id)) {
selected.value?.remove(lastSelectedId)
} else {
selected.value?.set(lastSelectedId, glist)
}
selected.value = selected.value //Notify observers
}
fun getSelected(): LiveData<HashMap<String,Glist>> {
return selected
}
fun onItemClicked(glist: Glist, short: Boolean) {
if (!short || selected.value!!.size != 0) {
toggleSelected(glist)
} else {
selected.value?.clear()
_navigateToItem.value = glist.id
}
}
...
}
MainFragment
viewModel.getSelected().observe(viewLifecycleOwner, Observer {
adapter.setSelected(it.keys.toList(),viewModel.lastSelectedId)
})
MainAdapter
class MainAdapter(private val clickListener: MainAdapterListener):
ListAdapter<QueryItem<Glist>, MainAdapter.ViewHolder>(asyncDifferConfig) {
...
private val selected = HashSet<String>()
fun setSelected(list: List<String>, lastChangedId: String) {
selected.clear()
selected.addAll(list)
for (i in 0 until currentList.size) {
if (currentList[i].id == lastChangedId) {
notifyItemChanged(i)
return
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item,clickListener,selected.contains(item.id))
}
...
class ViewHolder private constructor(val binding: ListMainBinding): RecyclerView.ViewHolder(binding.root){
fun bind(item: QueryItem<Glist>, clickListener: MainAdapterListener, isSelected: Boolean) {
binding.glist = item.item
binding.clickListener = clickListener
binding.isSelected = isSelected
binding.executePendingBindings()
}
...
}
}