53

I have an issue with my RecyclerView and ListAdapter.

Through an API, I receive items in a ascending order from older to newer.

My list is then being refreshed by calling the submitList(items) method.

However, since everything is asynchronous, after receiving the first item, the RecyclerView remains on the position of the first item received and showed. Since in the ListAdapter class there is no callback when the submitList() method completed, I cannot find a way to scroll after the updates to one of the new items that has been added.

Is there a way to intercept when ListAdapter has been updated ?

Firdous nath
  • 1,487
  • 1
  • 14
  • 38
gbaccetta
  • 4,449
  • 2
  • 20
  • 30
  • 1
    Add code. Should you be using ListAdapter for RecyclerView? Arent ListAdapters used for ListViews? Maybe use the recyclerview adapter? https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter Thta thing definitely has notifyDataSetChanged() , register a AdapterDataObserver and call the function to scroll to the end. – Nephilim Nov 11 '18 at 14:06
  • 1
    Hello, I'm speaking about the new RecyclerView.ListAdapter, not the old one for ListView. Anyway the solution was indeed to to use an AdapterDataObserver! so thank you very much. If you post it as an answer I can accept it. – gbaccetta Nov 12 '18 at 16:37
  • Hey, I don't think my comment is eloquent or good enough to be an answer in itself, if you don't mind, add some of your fixed code, some explanations what worked and post it as the answer :) @gbaccetta – Nephilim Nov 14 '18 at 15:00
  • Were you ever able to get this resolved? I'm having the same issue. – Bink Jan 15 '19 at 17:57
  • @gbaccetta which callbacks do you need to override? – Etienne Lawlor Mar 07 '19 at 21:07
  • Please add your code – Vasudev Vyas Mar 14 '19 at 08:39

6 Answers6

56

Kotlin : Very simple method to do this.

listAdapter.submitList(yourNewList) { 
    yourRecyclerView.scrollToPosition(0) 
}
gallosalocin
  • 835
  • 1
  • 12
  • 18
  • 2
    This is the best answer. That's exactly what this callback is made for. – Florian Walther Feb 09 '21 at 13:21
  • 1
    Thank you Florian. I'm a big fan of you. I learn a lot of things from you so I'm happy to help. See you on YouTube. – gallosalocin Feb 10 '21 at 08:40
  • I wish I completely read the documentation This is precisely what I was looking for! – hellaandrew Mar 26 '21 at 07:25
  • What should I do if the list update animation, and scroll to top operation is too early? – jack guan Mar 27 '21 at 09:28
  • 4
    The reason this answer is not the accepted one is that, although it's great for scrolling to top every time a new list is submitted, it provides less flexibility(e.g. I need to scroll to top only when new items are inserted, not deleted, or moved around). – Max Jun 12 '21 at 07:41
42

Not specific to ListAdapter, but it should work nonetheless:

Just use adapter.registerAdapterDataObserver(RecyclerView.AdapterDataObserver) and override the relevant onItemXXX method to tell your RecyclerView to scroll to whatever position.

As an example:

    adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
        override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
            (recycler_view.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(positionStart, 0)
        }
    })
David Liu
  • 9,426
  • 5
  • 40
  • 63
  • 1
    Doesn't work in my case : for some reason none of the methods are called – Androidz Jan 18 '20 at 16:05
  • 1
    Then you have a bug in your adapter implementation that's not calling the notify methods appropriately. If you're using ListAdapter, make sure you call `submitList`. If you're making your own adapter implementation, then you need to notify the items yourself. – David Liu Jan 23 '20 at 02:28
  • 1
    The problem was in how native submitList() works: it will not be changed (none of the notify() methods will be called) if you submit the same list again, even if the contents change. – Androidz Jan 23 '20 at 10:04
  • It worked, thanks, I would really appreciate it if Google would simplify this, I mean it should be possible to just set a flag in your adapter stating that you are using a reverse list so you need this behavior. – David Jul 16 '22 at 04:49
7

I have checked ListAdapter source code and I see that there is onCurrentListChanged method.

You can simply override it in your custom adapter. You can also pass a listener to the adapter to catch the event and scroll your RecyclerView to the beginning.

Your adapter:

typealias ListChangeListener = () -> Unit

class Adapter(private val listChangeListener: ListChangeListener) : ListAdapter<Item, Adapter.ViewHolder>(Callback) {

    override fun onCurrentListChanged(previousList: List<Item>, currentList: List<Item>) {
        super.onCurrentListChanged(previousList, currentList)

        // Call your listener here
        listChangeListener()
    }

    // Other methods and ViewHolder class
}

Create the adapter in your Activity or Fragment:

recyclerView.adapter = Adapter { recyclerView.scrollToPosition(0) }
Anton Holovin
  • 5,513
  • 1
  • 31
  • 33
6

The method to override is called:

public void onCurrentListChanged(List<T> previousList, List<T> currentList)

As the documentation reads:

Called when the current List is updated.


The inline documentation of the ListAdapter reads:

This class is a convenience wrapper around AsyncListDiffer that implements Adapter common default behavior for item access and counting.

And later on it reads:

Advanced users that wish for more control over adapter behavior, or to provide a specific base class should refer to AsyncListDiffer, which provides custom mapping from diff events to adapter positions.

This means, you'd need to extend the regular RecyclerView.Adapter, combined with a custom implementation of the AsyncListDiffer; and not the ListAdapter (which is just a convenience wrapper, with the stated basic functionality).

One can use the ListAdapter class as a template for that (not extending it is the clue), but the resulting class can then be extended and reused. else there is no chance to get control over the desired callback.

Martin Zeitler
  • 1
  • 19
  • 155
  • 216
  • I'm not sure why but i don't have access to that method. public class SearchAdapter extends ListAdapter { // ... } . This is my import : import androidx.recyclerview.widget.ListAdapter; – Etienne Lawlor Mar 07 '19 at 21:24
  • @toobsco42 it's a `public` method, therefore it should be exposed. the signature probably should be `ListAdapter` instead... where the `ItemViewHolder extends androidx.recyclerview.widget.RecyclerView.ViewHolder`. – Martin Zeitler Mar 07 '19 at 21:31
  • I need it to be ListAdapter since it has multiple view types so there are multiple ViewHolder classes. I actually am looking at the source code and those methods don't exist. Its the recyclerview-1.0.0-sources.jar which i guess is part of this dependency : implementation 'androidx.core:core:1.0.0'. – Etienne Lawlor Mar 07 '19 at 21:36
  • @toobsco42 passing a generic `RecyclerView.ViewHolder` appears legal, but one can only pass one kind of data-model... it inherits the same `.getItemViewType()`. – Martin Zeitler Mar 07 '19 at 21:42
  • The different data models im using in this adapter implement an Item marker interface I created. – Etienne Lawlor Mar 07 '19 at 21:44
  • 1
    it needs to be implemented in the [AsyncListDiffer.ListListener](https://developer.android.com/reference/androidx/recyclerview/widget/AsyncListDiffer.ListListener.html), not the `ListAdapter` itself. the docs might be inaccurate... because I couldn't find it there either, when hitting "go to source". – Martin Zeitler Mar 07 '19 at 21:56
  • What does that event look like? AsyncListDiffer is a final member variable in ListAdapter. – Etienne Lawlor Mar 07 '19 at 22:00
  • @toobsco42 added my comments into the answer. – Martin Zeitler Mar 07 '19 at 22:27
0

For my case, just override onCurrentListChanged in your adapter class and call notifyDataSetChanged()

override fun onCurrentListChanged(
    previousList: MutableList<InboxMessage>,
    currentList: MutableList<InboxMessage>
) {
    super.onCurrentListChanged(previousList, currentList)
    notifyDataSetChanged()
}
Jiyeh
  • 5,187
  • 2
  • 30
  • 31
-1
listAdapter.submitList(yourNewList) { yourRecyclerView.smoothScrollToPosition(0) }
Shivanand Darur
  • 3,158
  • 1
  • 26
  • 32