Hello! Has anyone implemented a RecyclerView Adapter
, which implements filtering via Filter and in which additional items can be updated via payloads? How to synchronize two lists, the original and the filtered one, when you want to update an element, but you already used a filtering? Does anyone know any good examples?
PS: on top of that, there are a lot of elements in the adapter and updating occurs via AsyncListDiffer
.
Thank you all in advance for your answers!
The main problem is that when I enter a query into the search, I get filtered items, add one of the items to favorites (marked with an asterisk in the item), but when I clear the search, the list item is not a favorite (the asterisk is not displayed). Everything is also complicated by how to correctly update list elements during AsyncListDiffer
, so as not to accidentally update them while the list is being asynchronously updated.
I suspect the problem is in the submitList
and updateItem
method:
class Adapter(
private val listener: AdapterListener
): RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {
private val diffCallback = object : DiffUtil.ItemCallback<UiModel>() {
override fun areItemsTheSame(oldItem: UiModel, newItem: UiModel): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: UiModel, newItem: UiModel): Boolean {
return (
oldItem.name == newItem.name &&
oldItem.id == newItem.id &&
oldItem.isFavorite == newItem.isFavorite
)
}
}
private var listDiffer = AsyncListDiffer(this, diffCallback)
private val initialDataItems = mutableListOf<UiModel>()
private val dataFilter = object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val queryRawName = constraint?.toString()
if (queryRawName.isNullOrEmpty()) return FilterResults().apply { values = initialDataItems }
val queryName = queryRawName.lowercase().trim()
val filteredItems = initialDataItems.filter { it.name?.lowercase()?.contains(queryName).isTrue() }
return FilterResults().apply { values = filteredItems }
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
val values = results?.values as? List<*> ?: return
val filteredValues = values.filterIsInstance(UiModel::class.java)
if (values.size != filteredValues.size) return
listDiffer.submitList(filteredValues)
}
}
override fun getItemCount() = listDiffer.currentList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = parent.inflate(R.layout.item_layout)
return CustomViewHolder(
view = view,
listener = { listener.onClickFavorite(uiModel) }
)
}
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int,
payloads: MutableList<Any>
) {
val payloadValid = payloads.isNotEmpty() && payloads[0] is UiModel
if (payloadValid && holder is CustomViewHolder) {
holder.updatePayload(payloads[0] as UpdateUiModel)
} else {
super.onBindViewHolder(holder, position, payloads)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
(holder as? CustomViewHolder)?.bind(item)
}
override fun getFilter() = dataFilter
fun submitList(data: List<UiModel>, updateInitialItems: Boolean = true) {
listDiffer.submitList(data)
if (updateInitialItems) {
initialDataItems.clear()
initialDataItems.addAll(data)
}
}
fun updateItem(payload: UpdateUiModel) {
if (payload.id == null) return
listDiffer.currentList.forEachIndexed { index, dataItem ->
if (dataItem.id == payload.id) {
updateModel(index, payload)
notifyItemChanged(index, payload)
}
}
}
private fun isCorrectPosition(position: Int) = position in 0 until itemCount
private fun updateModel(index: Int, payload: UiModel) {
val currentList = listDiffer.currentList.toMutableList()
if (isCorrectPosition(index)) currentList[index] = currentList[index].updateModel(payload)
submitList(currentList)
}
private fun getItem(position: Int) = listDiffer.currentList[position]
}
Yes, it really need to update the initial list. What I changed:
submitList
method:
fun submitList(data: List<UiModel>) {
listDiffer.submitList(data)
initialDataItems.clear()
initialDataItems.addAll(data)
}
updateItem
method (the code can be improved, I wrote it quickly):
fun updateItem(payload: UpdateUiModel) {
if (payload.id == null) return
listDiffer.currentList.forEachIndexed { index, dataItem ->
if (dataItem.id == payload.id) {
updateModel(index, payload)
}
}
val newInitialList = initialDataItems.toMutableList()
initialDataItems.forEachIndexed { index, dataItem ->
if (dataItem.id == payload.id) {
if (isCorrectPosition(index)) newInitialList[index] = newInitialList[index].updateModel(payload)
}
}
initialDataItems = newInitialList
}
Note that at the end I do initialDataItems = newInitialList
in order to completely update the initial list.
And the last method:
private fun updateModel(index: Int, payload: UiModel) {
val currentList = listDiffer.currentList.toMutableList()
if (isCorrectPosition(index)) currentList[index] = currentList[index].updateModel(payload)
listDiffer.submitList(currentList)
}