Search code examples
androidkotlinandroid-recyclerviewlistadapter

RecyclerView - Stay on 0 position after notifiyItemInserted(0)


I have a chat in my application and I want my RV stay on position 0 when the new message comes and the first visible item position is 0, but new messages are added above the currently visible one, but the visible items stay in view so user has to scroll to see new message.

My adapter was extended from RecyclerView.Adapter<RecyclerView.ViewHolder> and I found a little hacky solution to get it work:

private suspend fun update(newItems: List<IChatItem>) {
    var recyclerViewState: Parcelable? = null
    if ((recyclerView?.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() == 0) {
        recyclerViewState = recyclerView?.layoutManager?.onSaveInstanceState()
    }

    val diffResult = withContext(Dispatchers.Default) {
        DiffUtil.calculateDiff(Diff(this.currentItems, newItems))
    }

    diffResult.dispatchUpdatesTo(this)

    recyclerViewState?.let {
        recyclerView?.layoutManager?.onRestoreInstanceState(recyclerViewState)
    }
}

The thing is saving RV state and after DiffUtil completes it's work, restore it. It did work, but now I'm trying to implement new adapter and extend my adapter class from ListAdapter because it launches DiffUtil in another thread. It works good, but the problem is I can't implement my previous solution here, it just doesn't work.

I tried following:

override fun submitList(list: MutableList<IChatItem>?) {
    var recyclerViewState: Parcelable? = null
    if ((recyclerView?.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() == 0) {
        recyclerViewState = recyclerView?.layoutManager?.onSaveInstanceState()
    }

    super.submitList(list)

    recyclerViewState?.let {
        recyclerView?.layoutManager?.onRestoreInstanceState(recyclerViewState)
    }
}

But this doesn't work anymore. Does anybody have a solution?

PS: I found this question - Inserting RecyclerView items at zero position - always stay scrolled to top, but I don't think this is the best solution to add empty invisible items in adapter's list. Any other ideas?

UPD: as I thought - because this adapter launches DiffUtil in another thread, it restored RV state before DiffUtil completes it's job. This code works:

override fun submitList(list: List<IChatItem>?) {
    var recyclerViewState: Parcelable? = null
    if ((recyclerView?.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() == 0) {
        recyclerViewState = recyclerView?.layoutManager?.onSaveInstanceState()
    }


    super.submitList(list)

    recyclerViewState?.let {
            Handler().postDelayed( 
                { 
                    recyclerView?.layoutManager?.onRestoreInstanceState(recyclerViewState) 
                }, 1000
            )
    }
}

But ofc this is not what I want. Is there any way to detect when DiffUtil is done with everything?


Solution

  • You can use RecyclerView.AdapterDataObserver() and handle what you need.

    Create your listener class that implement RecyclerView.AdapterDataObserver()

    and in your adapter constructor call registerAdapterDataObserver(your listener)

    and then handle your callbacks.

    See RecyclerView.AdapterDataObserver