AsyncListDiffer onAfterSubmitList Listener

1.6k Views Asked by At

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?

2

There are 2 best solutions below

0
On BEST ANSWER

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!

0
On

You can get advantage from registerAdapterDataObserver methods by listening to them (or what your needs need) i.e:

listAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
            override fun onChanged() {
                //
            }

            override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {

            }

            override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {

            }

            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {

            }

            override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {

            }

            override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {

            }
        })

You can clear the register of your adapterObserver once needed using unregisterAdapterDataObserver.