2

I have endless scroll in my recyclerview, so, it will update when there is new data. and i am using DiffUtil to update data in the recyclerview. DiffUtil does updates the data but whenever there is update data, recyclerview scroll to top and what it looks like is "using the notifydatasetchanged()". here is my DiffUtil and my adapter to update data.

class ProductDiffUtil(
    val oldProductList: List<ProductModel>, val newProductList: List<ProductModel>
) : DiffUtil.Callback() {

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldProductList[oldItemPosition].id == newProductList[newItemPosition].id
    }

    override fun getOldListSize(): Int = oldProductList.size

    override fun getNewListSize(): Int = newProductList.size

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldProductList[oldItemPosition] == newProductList[newItemPosition]
    }

    override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
        return super.getChangePayload(oldItemPosition, newItemPosition)
    }
}

Here is my adapter to update data

fun addProductList(productList: List<ProductModel>?) {
        val diffResult = DiffUtil.calculateDiff(ProductDiffUtil(this.productList, productList!!))
        this.productList.addAll(productList)
        diffResult.dispatchUpdatesTo(this)
    }

please help me with this. it is working fine when i am using notifyItemRangeChanged()... so what should i use to update data in recyclerview for best practice.

https://drive.google.com/open?id=1SapXW2wusmTpyGCRA9fa0aSLCYNL1fzN

Win Phyoe Thu
  • 213
  • 2
  • 9

3 Answers3

3

You're comparing the previous contents against only the new items, rather than against the list with all of them added.

Imagine if this.productList is currently 1,2,3, and the new productList is 4,5,6. When you run

DiffUtil.calculateDiff(ProductDiffUtil(this.productList, productList!!)

It will compare 1 to 4, 2 to 5, etc. and conclude that everything has changed and no new items have been added. (note: this is an oversimplification of the DiffUtil algorithm, but serves to illustrate the point)

Instead, if you want to use DiffUtil:

val oldList = ArrayList(productList)
this.productList.addAll(productList)
val diffResult = DiffUtil.calculateDiff(ProductDiffUtil(oldList, productList!!)
diffResult.dispatchUpdatesTo(this)

or, since you know exactly how many items are added and where, just use notifyItemRangeInserted and avoid the copy:

val oldSize = this.productList.size
this.productList.addAll(productList)
notifyItemRangeInserted(oldSize, productList.size)
Ryan M
  • 18,333
  • 31
  • 67
  • 74
  • fun addProductList(productList: List?) this method update only new list data from api. so, if i do so, size of the list will not be changed. example, this.productlist is 1,2,3 and the new product list is 4,5,6. it will not contain 1,2,3,4,5,6. i have tried as you said but it doesn't meet with my need. How can i get this? can you show me the example or others? – Win Phyoe Thu Feb 21 '20 at 07:30
  • Ah, I see - that wasn't clear from the question. I've updated my answer to take that into account - take a look and see if that fixes your problem. That makes a lot more sense, given the behavior. – Ryan M Feb 21 '20 at 08:15
  • Thank you for your answer. it helps me solved my problem. sorry for late replay bro. – Win Phyoe Thu Feb 26 '20 at 03:21
0

Consider making a generic diffUtil class instead of creating it for each adapter.

fun <T>diffList(oldList: List<T>, newList: List<T>, sameItem: (a: T, b: T) -> Boolean): DiffUtil.DiffResult {
val callback: DiffUtil.Callback = object : DiffUtil.Callback() {
    override fun getOldListSize(): Int {
        return oldList.size
    }

    override fun getNewListSize(): Int {
        return newList.size
    }

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return sameItem(oldList[oldItemPosition], newList[newItemPosition])
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition] == newList[newItemPosition]
    }
}

return DiffUtil.calculateDiff(callback) }

You can use it in your adapter like this:

 fun setItems(products: List<Product>) {
    val oldList = productList
    productList = products
    diffList(oldList, products, sameItem = { a, b -> a.id == b.id }).dispatchUpdatesTo(this)
}
Jonas Simonaitis
  • 170
  • 1
  • 13
-1

Check if the layout manager has already been set and get the current scroll position. Like this:

var itemPostion= 0
if(myRecyclerView.layoutmanager != null){
  itemPostion = (myRecyclerView.layoutmanager as LinearLayoutManager)
.findFirstCompletelyVisibleItemPosition()
}

You can have a look at this sample project on GitHub

mementoGuy
  • 391
  • 2
  • 8
  • This shouldn't be necessary, they shouldn't be replacing the adapter. – Ryan M Feb 20 '20 at 21:17
  • @RyanMentley it's not replacing the adapter. It restores the last previous scroll position which is what he wanted to solve instead of scrolling back to top on data update. – mementoGuy Feb 20 '20 at 23:41
  • RecyclerView doesn't scroll to the top by default on updates. This shouldn't be necessary. – Ryan M Feb 20 '20 at 23:45