c0dehunter
c0dehunter

Reputation: 6150

ListAdapter not refreshing RecyclerView if I submit the same list with different item order

I am using a RecyclerView with ListAdapter (which uses AsyncListDiffer to calculate and animate changes when list is replaced).

The problem is that if I submit() some list, then re-order that list, and submit() again, nothing happens.

How do I force ListAdapter to evaluate this new list, even though it is "the same" (but order has changed)?

New findings:

I checked source code of submitList() and in the beggining there is a check:

public void submitList(@Nullable final List<T> newList) {
        final int runGeneration = ++this.mMaxScheduledGeneration;
        if (newList != this.mList) {

I think this is the problem. But how to get past it? I mean, surely the developers thought about submiting a different ordered list?

Upvotes: 19

Views: 17070

Answers (8)

Carson Holzheimer
Carson Holzheimer

Reputation: 2963

Instead of

submitList(mySameOldListThatIModified)

You have to submit a new list like this:

ArrayList newList = new ArrayList(oldList);
newList.add(somethingNew); // Or sort or do whatever you want
submitList(newList);

It's kind of a problem with the API. We would expect ListAdapter to keep a copy of the list, but it doesn't, probably for memory reasons. When you change your old list, you are actually changing the same list that ListAdapter has stored. When ListAdapter checks if (newList != this.mList) both newList and mList are referring to the same list instance, so no matter what you have changed on that list, it will equal itself, and ignore your update.

In kotlin you can create a new list via:

val newList = oldList.toMutableList() // Unintuitive way to copy a list
newList[0] = newList[0].copy(isFavourite = false) // Do whatever modifications you want
submitList(newList)

Note that you cannot do this:

newList.first().isFavourite = false

because that will also change the first item in your old list, and again ListAdapter won't see a difference between the first item in your old list and the first item in your new list. I would recommend that all items in your list have val properties exclusively, to avoid this problem.

Upvotes: 28

Heriberto Rivera
Heriberto Rivera

Reputation: 421

If you have enable setHasFixedSize(true), remove this line.

"RecyclerView can perform several optimizations if it can know in advance that RecyclerView's size is not affected by the adapter contents...."

Upvotes: 1

Samnang CHEA
Samnang CHEA

Reputation: 651

The issue is that the new list submitted is not rendered.

androidx.recyclerview:recyclerview:1.2.0-beta02

The temporary solution is to smooth-scroll to any position after the new list committed, I do to the top.

movieListAdapter.submitList(list) {
    binding.recycler.smoothScrollToPosition(0)
}

Upvotes: 1

Nurseyit Tursunkulov
Nurseyit Tursunkulov

Reputation: 9380

I had a similar problem but the incorrect rending was caused by a combination of setHasFixedSize(true) and android:layout_height="wrap_content". For the first time the adapter was supplied with an empty list so the height never got updated and was 0. Anyway, this resoved my issue. Someone else might have the same problem and will think it is problem in the adapter.

android recyclerview listadapter example, RecyclerView ListAdapter. ListAdapter is a RecyclerView adapter that displays a list. This is available in RecyclerView 27.1+, and the same function also exists in the AsyncListDiffer class if you cannot extend the adapter. ListAdapter helps you to work with RecyclerViews that change the content over time. usersList.observe(this, list -> adapter.submitList(list)); recyclerView.setAdapter(​adapter); } } class UserAdapter extends ListAdapter<User,

all credits to this man: https://www.xspdf.com/help/50031492.html

Upvotes: 1

Rafa Araujo
Rafa Araujo

Reputation: 344

Just call listAdapter.notifyDataSetChanged() and the ListAdapter will redraw the list based on submitted values.

Upvotes: -3

UnOrthodox
UnOrthodox

Reputation: 146

To add to Carson's answer, which is a better way to do it, you can maintain the benefits of submitList as follows in Kotlin:

submitList(oldList.toList().toMutableList().let {
     it[index] = it[index].copy(property = newvalue) // To update a property on an item
     it.add(newItem) // To add a new item
     it.removeAt[index] // To remove an item
     // and so on....
     it
})

Upvotes: 1

aLL
aLL

Reputation: 1726

It defeats ListAdapter's purpose for automatically calculating and animating list changes when you call these lines consecutively:

submitList(null);
submitList(orderChangedList);

Meaning, you only cleared (null) the ListAdapter's currentList and then submitted ( .submitList()) a new List. Thus, no corresponding animation will be seen in this case but only a refresh of the entire RecyclerView.

Solution is to implement the .submitList( List<T> list) method inside your ListAdapter as follows:

public void submitList(@Nullable List<T> list) {
    mDiffer.submitList(list != null ? new ArrayList<>(list) : null);
}

This way you allow the ListAdapter to retain its currentList and have it "diffed" with the newList, thereby the calculated animations, as opposed to "diffing" with a null.

Note: However, no update animation will happen, if, of course, the newList contains the same items in the same order as the originalList.

Upvotes: 3

Manoj Perumarath
Manoj Perumarath

Reputation: 10214

That function will not get called because ListAdapter won't consider it as another list, since it has all the same items only the order got changed.

@Override
public void submitList(@Nullable final List<T> list) {
super.submitList(list != null ? new ArrayList<>(list) : null);
}

So to solve this, you need to call this function with null first, then immediately call with the order changed list.

submitList(null);
submitList(orderChangedList);

Upvotes: 0

Related Questions