Android: SortedList with duplicates

本小妞迷上赌 提交于 2019-12-21 08:06:50

问题


I have some problems understanding RecyclerViews SortedList.

Lets say I have a very simple class only having a very simple class holding data:

public class Pojo {
    public final int id;
    public final char aChar;

    public Pojo(int id, char aChar) {
        this.id = id;
        this.aChar = aChar;
    }

    @Override
    public String toString() {
        return "Pojo[" + "id=" + id
                + ",aChar=" + aChar
                + "]";
    }
}

My understanding is that the sorted list won't contain any duplicates.

But when I have a SortedList with callbacks like this:

....

@Override
public boolean areContentsTheSame(Pojo oldItem, Pojo newItem) {
    return oldItem.aChar == newItem.aChar;
}

@Override
public int compare(Pojo o1, Pojo o2) {
    return Character.compare(o1.aChar, o2.aChar);
}

@Override
public boolean areItemsTheSame(Pojo item1, Pojo item2) {
    return item1.id == item2.id;
}

I end up with duplicates when I add multiple items with the same id but different chars.

sortedList.add(new Pojo(1, 'a'));
sortedList.add(new Pojo(1, 'b'));

I would expect the list to update the item. Instead now I have multiple items even though areItemsTheSame returned true.


回答1:


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.




回答2:


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;
}



回答3:


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.




回答4:


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);
}



回答5:


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




回答6:


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.



来源:https://stackoverflow.com/questions/31853678/android-sortedlist-with-duplicates

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!