RecyclerView scroll to top with AsyncListDiffer not working

3.4k Views Asked by At

I am using RecyclerView with AsyncListDiffer (calculates and animates differences between old and new items, all on background thread).

I have a button to sort the list. After I sort it and re-set it to RecyclerView using mDiffer.submitList(items); I also call recyclerView.scrollToPosition(0) or (smoothScrollToPosition(0)), but it has no effect.

I think this behaviour is expected, as AsyncListDiffer is probably still calculating differences at the time that scrollToPosition(0) is called, so it has no effect. Additionally, by default AsyncListDiffer does not scroll back to top, but instead it keeps RecyclerView in the same state.

But how do I tell the RecyclerView to scroll to top after AsyncListDiffer is done and updates it?

4

There are 4 best solutions below

1
On BEST ANSWER

This got answered here:

https://stackoverflow.com/a/55264063/1181261

Basically, if you submit the same list with different order, it will be ignored. So first you need to submit(null) and then submit your re-ordered list.

1
On

I am concerned that while .submitList(null) may have worked for you, it only refreshed your entire RecyclerView without rendering the desired animated list updates.

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 animated updates, as opposed to "diffing" with a null.

2
On

Use registerAdapterDataObserver on the adapter in the activity, right after your "mRecyclerView.setAdapter(adapter)" code:

adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {

    public void onChanged() {
       mRecyclerView.scrollToPosition(0);
    }

    public void onItemRangeRemoved(int positionStart, int itemCount) {
        // same or similar scroll code
        }
    }
});

Then unregister the observer in onStop() in the activity.

1
On

If you look at the javadocs for AsyncListDiffer's submitList, you will notice that the second param is a Runnable that is executed after the items are submitted to the adapter:

/**
 * Pass a new List to the AdapterHelper. Adapter updates will be computed on a background
 * thread.
 * <p>
 * If a List is already present, a diff will be computed asynchronously on a background thread.
 * When the diff is computed, it will be applied (dispatched to the {@link ListUpdateCallback}),
 * and the new List will be swapped in.
 * <p>
 * The commit callback can be used to know when the List is committed, but note that it
 * may not be executed. If List B is submitted immediately after List A, and is
 * committed directly, the callback associated with List A will not be run.
 *
 * @param newList The new List.
 * @param commitCallback Optional runnable that is executed when the List is committed, if
 *                       it is committed.
 */

So what you want is this (this is in Kotlin btw):

adapter.submitList(items) {
   // This will run after items have been set in the adapter
   recyclerView.scrollToPosition(0)
}

or in Java

adapter.submitList(items, () -> {
    recyclerView.scrollToPosition(0);
});