11

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?

Xavier Rubio Jansana
  • 6,388
  • 1
  • 27
  • 50
c0dehunter
  • 6,412
  • 16
  • 77
  • 139
  • Have you tried to scroll *before* submitting the new items? Maybe it's not the solution you need, but at least can be useful to know if the operation is failing because `AsyncListDiffer` is still running or because something else. – Xavier Rubio Jansana Mar 20 '19 at 14:02
  • I just tried, and it does not help. I also just noticed that when I sort the list and submit it, the `RecyclerView` items are not re-bound (refreshed). I have to pull down a little and then I can see the sorted items. But this might be for another question.. – c0dehunter Mar 20 '19 at 14:18
  • Maybe this other questions may be of help https://stackoverflow.com/questions/41357303/recyclerview-scrolltoposition-does-not-work and https://stackoverflow.com/questions/30845742/smoothscrolltoposition-doesnt-work-properly-with-recyclerview And about the re-bound, looks like you may have an issue with stable Ids. Could you provide you Adapter code? Alt. see https://medium.com/@hanru.yeh/recyclerviews-views-are-blinking-when-notifydatasetchanged-c7b76d5149a2 – Xavier Rubio Jansana Mar 20 '19 at 14:57
  • Bottom line: either disable stable Ids in your adapter `adapter.setHasStableIds(false)` or, better, make sure they are enabled `adapter.setHasStableIds(true)` and that you override `long getItemId(int position)` in your adapter to return a **unique** Id for every element. – Xavier Rubio Jansana Mar 20 '19 at 14:59
  • @XavierRubioJansana thanks for your help, but I think this was not causing this. Please see my answer. – c0dehunter Mar 20 '19 at 15:16
  • 1
    Weird, but good to know. Thanks for sharing the solution! – Xavier Rubio Jansana Mar 20 '19 at 15:23
  • Possible duplicate of [ListAdapter not refreshing RecyclerView if I submit the same list with different item order](https://stackoverflow.com/questions/55263384/listadapter-not-refreshing-recyclerview-if-i-submit-the-same-list-with-different) – Andreas Mar 22 '19 at 08:28

4 Answers4

6

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.

c0dehunter
  • 6,412
  • 16
  • 77
  • 139
  • When you complete all these recycler view related queries, post some articles on medium which will be highly useful for noobies like me :) – Manoj Perumarath Mar 22 '19 at 04:42
5

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.

Andreas
  • 2,455
  • 10
  • 21
  • 24
aLL
  • 1,596
  • 3
  • 17
  • 30
  • 1
    By using this, list is not scrolled back to top after sorting and it takes 2-3sec before list order changes (animation shows). – c0dehunter Mar 22 '19 at 14:36
4

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);
});
Max Kohne
  • 41
  • 2
  • This doesn't always work. In my experience, the commitCallback is often executed before the RecyclerView (well, it's layoutManager) has a chance to actually re-bind and layout it's views, and so the call to `scrollToPosition(0)` ends up having no effect. With Kotlin, you could launch a coroutine and use a `delay()` before calling `scrollToPosition(0)`, but that seems hacky. – Bradleycorn Feb 11 '21 at 18:00
0

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.

AJW
  • 1,578
  • 3
  • 36
  • 77
  • What do you mean with "// same or similar scroll code" can you clarify, please? – MML Aug 11 '22 at 14:10
  • @MML Meaning you could put the same code in to scroll the screen up to the top, using "mRecyclerView.scrollToPosition(0);" or maybe you want to scroll the screen down to the bottom of UI so you would add code for that case. – AJW Aug 12 '22 at 15:33