Search code examples
androidandroid-recyclerviewkotlinnotifydatasetchanged

NotifyDataSetChanged does not update the RecyclerView correctly


I am trying to implement a fairly basic logic within my recyclerview adapter but notifyDataSetChanged() is giving me quite the headache.

I have a filter method that looks like this:

fun filter(category: Int) {
    Thread(Runnable {
        activeFiltered!!.clear()

        if (category == -1) {
            filterAll()
        } else {
            filterCategory(category)
        }

        (mContext as Activity).runOnUiThread {
            notifyDataSetChanged()
        }
    }).start()
}

where filterAll() and filterCategory() functions are quite easy:

private fun filterAll() {
    activeFiltered?.addAll(tempList!!)
}

private fun filterCategory(category: Int) {
    for (sub in tempList!!) {
        if (sub.category == category) {
            activeFiltered?.add(sub)
        }
    }
}

When I run this code and filter the list by category the activeFiltered list is updated correctly and contains the items I expect, but when notifyDataSetChanged() is run it only cuts the list's range without updating the items.

Is there a way to fix this?

I also tried, instead of notifyDataSetChanged() to use:

activeFiltered!!.forEachIndexed {index, _ ->  notifyItemChanged(index)}

but the problem is still there.

It isn't a threading issue either since I tried putting the whole logic in the main thread and the list still wasn't updated correctly.

This is my onBindViewHolder():

override fun onBindViewHolder(viewHolder: ActiveViewHolder, pos: Int) {
    sub = activeFiltered!![pos]
    inflateView()

}

This is where I inflate my text, sub is the instance variable set in the onBindViewHolder():

private fun inflateView() {
        viewHolder.title.text = sub.title
    }

Solution

  • It seems the implementation of onBindViewHolder() is incorrect. In order to update a list item, the passed in viewHolder parameter should be used (not the viewHolder you created in the onCreateViewHolder()).

    The correct implementation should be like

    override fun onBindViewHolder(viewHolder: ActiveViewHolder, pos: Int) {
        val sub = activeFiltered!![pos]
        inflateView(viewHolder, sub)
    }
    
    private fun inflateView(viewHolder: ActiveViewHolder, sub: <YourDataType>) {
        viewHolder.title.text = sub.title
    }
    

    By the way, it is not a good practice to hold something as a member field in order to access it in several methods. Feel free to pass it as arguments to such methods. In the above code I passed the sub as argument and not stored it as a member.

    And also it is not necessary to hold the viewHolder that you create in onCreateViewHolder(). We mostly need them in some callback methods (like onBindViewHolder(), etc) and these methods will receive the right viewHolder as arguments.