Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AsyncListDiffer onAfterSubmitList Listener

I'm trying to call recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView,null,0) only after mDiffer.submitlist(list) is finished "diffing" and animating the list updates/changes.

Is there an AsyncListDiffer feature for onAfterSubmitList(Callback callback) that I could use to achieve this?

If not, is there anyway to find out when does submitList() finish its task so I could put my scrollToPosition(0) in there?

like image 345
aLL Avatar asked Mar 25 '19 04:03

aLL


People also ask

How does the asynclistdiffer work?

The AsyncListDiffer can consume the values from a LiveData of List and present the data simply for an adapter. It computes differences in list contents via DiffUtil on a background thread as new List s are received.

What is the difference between listadapter and asynclistdiffer?

For simplicity, the ListAdapter wrapper class can often be used instead of the AsyncListDiffer directly. This AsyncListDiffer can be used for complex cases, where overriding an adapter base class to support asynchronous List diffing isn't convenient.

Is it possible to get a recyclerview callback from asynclistdiffer?

I dived into the source code of AsyncListDiffer, and I found that it's possible to receive a callback when all RecyclerView updates have been done - but the way to receive this callback is just bizarre.


1 Answers

Update July 2020:

Google added a callback for this! See: https://developer.android.com/reference/androidx/recyclerview/widget/AsyncListDiffer#submitList(java.util.List%3CT%3E,%20java.lang.Runnable)

Previous answer, from the bad old days:

First of all, I can't believe Google didn't provide a callback for this.

I dived into the source code of AsyncListDiffer, and I found that it's possible to receive a callback when all RecyclerView updates have been done - but the way to receive this callback is just bizarre.

You need to create a sub-class of BatchingListUpdateCallback, and then wrap your ListUpdateCallback (or AdapterListUpdateCallback as in most cases) inside it.

Then, you should override dispatchLastEvent. AsyncListDiffer will call this after all but one of the updates have been dispatched. In your dispatchLastEvent, you'll want to call the super implementation, so you don't break anything. You'll also want to invoke a custom callback, which is your way in.

In Kotlin that looks like:

class OnDiffDoneListUpdateCallback(
    listUpdateCallback: ListUpdateCallback,
    private val onDiffDoneCallback: () -> Unit
) : BatchingListUpdateCallback(listUpdateCallback) {
    override fun dispatchLastEvent() {
        super.dispatchLastEvent()
        onDiffDoneCallback()
    }
}

The last step is to then provide your custom OnDiffDoneListUpdateCallback to the AsyncListDiffer. To do this, you need to initialise the AsyncListDiffer for yourself - if you're using ListAdapter or something similar, you'll need to refactor so that you are in control of the AsyncListDiffer.

In Kotlin, that looks like:

private val asyncListDiffer = AsyncListDiffer<ItemType>(
        OnDiffDoneListUpdateCallback(AdapterListUpdateCallback(adapter)) {
            // here's your callback
            doTheStuffAfterDiffIsDone()
        },
        AsyncDifferConfig.Builder<ItemType>(diffCallback).build()
    )

Edit: I forgot about the edge-cases, of course!

Sometimes, dispatchLastEvent isn't called, because AsyncListDiffer considers the update trivial. Here's the checks it does:

    if (newList == mList) {
        ...
        return;
    }

    // fast simple remove all
    if (newList == null) {
        ...
        mUpdateCallback.onRemoved(0, countRemoved);
        return;
    }

    // fast simple first insert
    if (mList == null) {
        ...
        mUpdateCallback.onInserted(0, newList.size());
        return;
    }

I recommend doing these checks for yourself, just before you call asyncListDiffer.submitList(list). But of course, it couldn't be that easy! mList is private, and getCurrentList will return an empty list if mList is null, so is useless for this check. Instead you'll have to use reflection to access it:

    val listField = AsyncListDiffer::class.java.getDeclaredField("mList")
    listField.isAccessible = true
    val asyncDifferList = listField.get(asyncListDiffer) as List<ItemType>?

    asyncListDiffer.submitList(newList)

    if(asyncDifferList == null) {
        onDiffDone()
    }
    if(newList == null) {
        onDiffDone()
    }
    if(newList == asyncDifferList) {
        onDiffDone()
    }

Edit 2: Now, I know what you're saying - surely there's an easier, less hacky way to do this? And the answer is... yes! Simply copy the entire AsyncListDiffer class into your project, and just add the callback yourself!

like image 108
Luke Avatar answered Sep 28 '22 07:09

Luke