8

I have this issue with the pagination infinite scroll in RecyclerView, I am adding all new item using .addAll()

 movieList.addAll(it.movieList)
 adapter.submitList(movieList)
 Log.wtf("WTF", movieList.size.toString())

The size keeps increasing whenever we get a success response from API which indicates that the list is indeed being populated but the item in RecyclerView stays the same and submitList() seems to work only at first call.

Here is my DiffUtil class and the Adapter

class DiffUtilMovies : DiffUtil.ItemCallback<MovieItem>() {

    // DiffUtil uses this test to help discover if an item was added, removed, or moved.
    override fun areItemsTheSame(oldItem: MovieItem, newItem: MovieItem): Boolean {
        return oldItem.id == newItem.id
    }

    // Check whether oldItem and newItem contain the same data; that is, whether they are equal.
    // If there are differences between oldItem and newItem, this code tells DiffUtil that the item has been updated.
    override fun areContentsTheSame(oldItem: MovieItem, newItem: MovieItem): Boolean {
        // Check for now if there is a difference on the price, removing specific fields
        // means checking all the data for changes
        return oldItem.title == newItem.title
    }

}


class MovieAdapter(private val context: Context) : ListAdapter<MovieItem, MovieAdapter.ItemView>(DiffUtilMovies()) {

    private var isDetached: Boolean = false

    class ItemView(itemView: MovieCardBinding) : RecyclerView.ViewHolder(itemView.root) {
        val titleTxt = itemView.titleTxt
        val rateTxt = itemView.rateTxt
        val rateBar = itemView.rateBar
        val imageThumb = itemView.thumbnail
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemView {
        return ItemView(
            MovieCardBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: ItemView, position: Int) {
        holder.apply {

            val movieItem = getItem(position)

            titleTxt.text = movieItem.title
            rateTxt.text = movieItem.voteAverage.toString()

            val rateAvg = movieItem.voteAverage?.toFloat() ?: run {
                0.0f
            }

            rateBar.rating = rateAvg/2

            if (!isDetached)
                GlideApp.with(context)
                    .load(context.getString(R.string.image_link,AppConfig.image_endpoint, movieItem.posterPath))
                    .thumbnail(GlideApp.with(context).load(R.drawable.loading).centerCrop())
                    .error(R.drawable.no_image)
                    .into(imageThumb)

            this.itemView.setOnClickListener {

                try {

//                    context.startActivity(Intent(context, AssetInfoActivity::class.java).apply {
//                        putExtra(context.getString(R.string.assets), movieItem)
//                    })

                }
                catch (ignored: Exception){
                    // The user probably already leave before the activity started
                }

            }

        }
    }

    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        super.onDetachedFromRecyclerView(recyclerView)
        isDetached = true
    }
}
Bitwise DEVS
  • 2,858
  • 4
  • 24
  • 67

1 Answers1

12

ListAdapter doesn’t work with mutable lists. This is because if you modify the contents of the List, when it tries to compare the contents of the old list and new list, it’s comparing the same list to itself. There is no old list instance that still holds the old contents, so it cannot detect any differences.

Instead of mutating the original list, you should create a new one, for example

movieList = movieList + it.movieList
adapter.submitList(movieList)

Alternatively, you can use a mutable backing list, but always create a copy when passing it to submitList. You must use a copy even the very first time you pass the List so it is never referring to your mutable List.

movieList.addAll(it.movieList)
adapter.submitList(movieList.toList())
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • Yeah just got it fixed right now. Anyway thanks, I will still mark this answer – Bitwise DEVS Nov 25 '21 at 13:49
  • However I still see some duplicate item, AFAIK DiffUtil should already remove duplicate entry right? – Bitwise DEVS Nov 25 '21 at 13:58
  • It does not remove duplicates. The items in the list after `submitList` will match exactly the items that are in the List you pass to it. – Tenfour04 Nov 25 '21 at 15:08
  • 1
    Ohh I thought it also includes avoidance of duplicate items. Thanks for the info. – Bitwise DEVS Nov 25 '21 at 19:20
  • Just a follow up question, since `DiffUtil` automatically handles which item gets updated. If my object is the same, why `DiffUtil` did not just update the existing instead of adding it to the list which cause duplication? Does `DiffUtil` only checks and handle object when there is an update to its attribute? – Bitwise DEVS Feb 17 '22 at 14:57
  • 1
    DiffUtil is not responsible for making any modifications to your lists. It's up to you to make sure the new list has exactly the items you want in the order you want them to appear. DiffUtil is only responsible for figuring out how to change the view holders representing the old displayed list to match your new list in the most efficient way. When you call `submitList()`, you are saying, "This new list is exactly what I want the ReyclerView to show. Make it happen." – Tenfour04 Feb 17 '22 at 15:05
  • @Tenfour04 Is there a way to do the above in Java? Trying to move items in a RecyclerView list that uses ListAdapter, and items won't drag. – AJW Jul 26 '23 at 02:35
  • @AJW, it works exactly the same in Java, just different syntax. Java Lists are almost always mutable, but you can just make sure you always use new List instances when you pass them to submitList(). – Tenfour04 Jul 26 '23 at 03:59
  • @Tenfour04 I tried "ArrayList copiedList = new ArrayList<>(currentList); adapter.submitList(copiedList);" No luck, any idea why? – AJW Jul 26 '23 at 04:03
  • You need to copy it *before* you make any changes to the list, and only change the new one. Otherwise, your old list looks like the new one so DiffUtil cannot detect any changes. – Tenfour04 Jul 26 '23 at 04:06
  • 1
    @Tenfour04 I did that. I will post a new question tomorrow night when I have time, to show all of the code. – AJW Jul 26 '23 at 04:09
  • @Tenfour04 I posted my question here: https://stackoverflow.com/q/76807960/3796660. Would appreciate any thoughts or comments on it. – AJW Aug 04 '23 at 04:39