I have some problems understanding RecyclerView
s SortedList
.
Lets say I have a very simple class only having a very simple class holding data:<
In Java a collection that contains no duplicate elements is Set. The common Implementing Classes are HashSet and TreeSet. You are wrong assuming that SortedList does that.
I had a similar issue when creating a chat app which I need to update messages by their IDs and sort them by their dates. The support lib's SortedList does not do that or at least I had little time to dive into its source code and test. So, I created a small component, MultiSortedList:
import android.support.v7.widget.RecyclerView
/**
* Created by abduaziz on 6/14/18.
*
* MultiSortedList is a wrapper component to ArrayList that keeps its elements in a sorted order
* using UpdateCallbackInterface. It is intended to be used inside recycler view adapters.
*
* */
class MultiSortedList<T>(var updateCallback: UpdateCallback<T>, var adapter: RecyclerView.Adapter<*>? = null) {
companion object {
val TAG = "SORTEDLIST"
}
// internal list to hold elements by sortBy() -> visible to user
private val list: ArrayList<T> = arrayListOf()
// internal list to hold elements by updateBy() -> not visible
private val uList: ArrayList<T> = arrayListOf()
// add adapter from ui
fun addAdapter(adapter: RecyclerView.Adapter<*>?) {
this.adapter = adapter
}
/*
* 1. Search for existing element that satisfies updateBy()
* 2. Remove the existing element if found
* 3. Add the new item with sortBy()
* 4. Notify if adapter is not null
* */
fun add(newItem: T) {
remove(newItem)
// save to internal list by updateBy()
var toBeStoredPosition = uList.binarySearch { updateCallback.updateBy(it, newItem) }
if (toBeStoredPosition < 0) toBeStoredPosition = -(toBeStoredPosition + 1)
uList.add(toBeStoredPosition, newItem)
// save to UI list and notify changes
var sortPosition = list.binarySearch { updateCallback.sortBy(it, newItem) }
if (sortPosition < 0) sortPosition = -(sortPosition + 1)
list.add(sortPosition, newItem)
adapter?.notifyItemInserted(sortPosition)
}
/*
* Remove and notify the adapter
* */
fun remove(removeItem: T) {
val storedElementPosition = uList.binarySearch { updateCallback.updateBy(it, removeItem) }
if (storedElementPosition >= 0 && storedElementPosition < uList.size) {
// remove from internal list
val itemTobeRemoved = uList[storedElementPosition]
uList.removeAt(storedElementPosition)
// remove from ui
val removePosition = list.binarySearch { updateCallback.sortBy(it, itemTobeRemoved) }
if (removePosition >= 0 && removePosition < list.size) {
list.removeAt(removePosition)
adapter?.notifyItemRemoved(removePosition)
}
}
}
// can be accessed -> list.get(position) or list[position]
operator fun get(pos: Int): T {
return list[pos]
}
// for adapter use
fun size(): Int {
return list.size
}
inline fun forEachIndexed(action: (Int, T) -> Unit) {
for (index in 0 until size()) {
action(index, get(index))
}
}
/*
* UpdateCallback is the main interface that is used to compare the elements.
* - sortBy() is used to locate new elements passed to SortedList
* - updateBy() is used to update/remove elements
*
* Typical example would be Message model class which we want to:
* - Sort messages according to their dates
* - Update/Remove messages according to their randomIDs or IDs.
* */
interface UpdateCallback<T> {
fun sortBy(i1: T, i2: T): Int
fun updateBy(oldItem: T, newItem: T): Int
}
}
The usage is explained here: https://medium.com/@abduazizkayumov/sortedlist-with-recyclerview-part-2-64c3e9b1b124
I think you should use Integer.compare(o1.id, o2.id);
in compare
method, it's where SortList decide those 2 items are the same or not.
You can check if the object already exist in the sorted list by doing this.
if (sortedList.indexOf(item) == -1)
{
sortedList.add(item); //Item still does not exist because index is -1
}
else
{
sortedList.updateItemAt(sortedList.indexOf(item), item);
}
As Minhtdh already mentioned in his answer, the problem lies within your compare()
.
See, add()
looks up the existing object's index by using the compare()
you implement. Therefore when your compare()
returns something other than 0 it adds the object to the list.
You would need to check if the items are the same before comparing it's contents. However, if your content can be the same you would need a secondary comparison.
This is how I would implement the compare()
in your case:
@Override
public int compare(Pojo o1, Pojo o2) {
int result;
if (areItemsTheSame(o1, o2) {
result = 0;
} else {
result = Character.compare(o1.aChar, o2.aChar);
if (result == 0) {
// TODO implement a secondary comparison
}
}
return result;
}
SortedList does not keep any mapping by ids (because there are no ids in the API). So when the sorting criteria changes (a to b in your case), SortedList cannot find the existing element.
You can keep the id mapping yourself, then have your add method as follows:
void add(Item t) {
Item existing = idMap.get(t.id);
if (existing == null) {
sortedList.add(t);
} else {
sortedList.updateItemAt(sortedList.indexOf(existing), t);
}
idMap.put(t.id, t);
}
You'll also need to implement a remove method to remove the item from the idMap.