RecyclerView ListAdapter does not update list correctly
Asked Answered
S

1

7

I'm using Android Room and Rx Flowable to update a RecyclerView will the object from my database. For better animation and performance, I updated the project to use the new ListAdapter in my project.

The problem is when user presses a Checkbox the Task object is updated as completed, the database reflects this change, but sometimes the adapter ListAdapter notifies my onBindViewHolder and sometimes it says that my view is already updated.

TaskListFragment

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    viewModel.loadTasks(onTasksLoaded = { adapter.submitList(it) })
}

TaskDao

@Query("SELECT * FROM task")
fun getAllTasks(): Flowable<MutableList<Task>>

TaskListAdapter

class TaskListAdapter : ListAdapter<Task, BindingHolder<*>>(TaskDiffCallback()) {

    override fun onBindViewHolder(holder: BindingHolder<*>, position: Int) {
        val task = getItem(position)
        // more binding code

        // THE ISSUE IS HERE
        if (task.completed) {
            holder.viewStrike.visibility = View.VISIBLE
        } else {
            holder.viewStrike.visibility = View.INVISIBLE
        }
    }

    // more adapter code
}

Task

data class Task(
    @ColumnInfo(name = "task_is_completed") var completed: Boolean = false,
    @ColumnInfo(name = "task_description") var description: String,
    @ColumnInfo(name = "task_category_id") var categoryId: Long? = null,
    @ColumnInfo(name = "task_due_date") var dueDate: Calendar? = null
) : Parcelable {

    @IgnoredOnParcel
    @ColumnInfo(name = "task_id")
    @PrimaryKey(autoGenerate = true)
    var id: Long = 0
}

DiffUtil

class TaskDiffCallback : DiffUtil.ItemCallback<Task>() {

    override fun areItemsTheSame(oldItem: Task, newItem: Task) =
        oldItem.id == newItem.id

    override fun areContentsTheSame(oldItem: Task, newItem: Task) =
        oldItem.task == newItem.task
}

Debugging the app, sometimes the isCompleted field updates correctly and sometimes the field is already updated during the Diff, so it says that the view has no updates. I couldn't find any pattern to this issue. In my case, sometimes the view holder.viewStrike changes its visibility and sometimes it remains in inconsistent state.

The issue seems related to the ListAdapter itself which sometimes sends the oldItem with the updated value, so there is no diff to be updated.

Am I missing any implementation?

Sihonn answered 27/11, 2018 at 16:19 Comment(3)
Did you every find the solution here? I might be running into a similar issue.Waive
@Bink, no I couln't find the solution. I debugged several times, replaced Rx for LiveData, tried different approaches in DiffUtil.ItemCallback with no success.Sihonn
I think the error is that AsyncListDiffer#submitList evaluates newList == mList, and If you just change an item, but not the (size) of the list, this statement is true and the update process is cancelled. But I didn't find a workaround yet.Runofthemill
M
0

It might be late, but I will write an answer for others. You should not use areContentsTheSame in this way. You have to compare values related this two object which can get changed. like:

 override fun areContentsTheSame(oldItem: Task, newItem: Task){
  return if(oldItem.completed == newItem.completed && oldItem.description == newItem.description && oldItem.dueDate == newItem.dueDate)}

But comparing values which can never get changed is not necessary like : id

Miscreated answered 4/8, 2020 at 6:6 Comment(1)
Hello! Thanks for your reply. But since I'm using data class, comparing by the way I'm doing, it will return false if any attribute is different. So, I can't see a huge difference between my approach and yours.Sihonn

© 2022 - 2024 — McMap. All rights reserved.