DiffUtil thư viện hỗ trợ tuyệt vời cho RecyclerView

Chắc hẳn khi làm việc với RecyclerView chúng ta đã quá quen với việc update dữ liệu bằng các hàm:

notifyDataSetChanged

notifyItemChanged

Và nhiều hàm cập nhật khác.

Tuy nhiên khi sử dụng DiffUtils chúng ta sẽ không cần quan tâm đến chúng nữa.

Mặc dù DiffUtil đã được giới thiệu từ rất lâu, tuy nhiên developer có vẻ khá ít sử dụng.

DiffUtil là một class tiện ích phát hiện sự khác biệt giữa hai danh sách từ đó chuyển đổi và cập nhật dữ liệu tự động.

Một số phương thức trong DiffUtil :

getOldListSize()– Trả về số lượng của list cũ

getNewListSize()– Trả về số lượng của list mới

areItemsTheSame(int oldItemPosition, int newItemPosition)– Kiểm tra xem 2 đối tượng có cùng Items hay là không

areContentsTheSame(int oldItemPosition, int newItemPosition)– Kiểm tra xem 2 Items có cùng dữ liệu hay là không. Phương thức này chỉ được gợi khi areItemsTheSame() trả về true.

getChangePayload(int oldItemPosition, int newItemPosition)– Nếu areItemTheSame() trả về true và areContentsTheSame() trả false sau đó DiffUtil sẽ gọi phương thức này để trả về sự thay đổi.

Cùng tham khảo 1 sample mẫu về cách triển khai DiffUtil:

class UserDiffUtilCallback(private val oldList: List<Users>, private val newList: List<Users>) : 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 oldList[oldItemPosition].id == newList[newItemPosition].id
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return when {
            oldList[oldItemPosition].id == newList[newItemPosition].id -> true
            oldList[oldItemPosition].name == newList[newItemPosition].name -> true
            else -> false
        }
    }

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

}

Nhìn chung cách triển khai khá đơn giản và dễ thực hiện.

areItemsTheSame dựa vào id để xem 2 item có phải giống nhau 2 không.

areContentsTheSame so sánh nội dung bên trong để quyết định có cập nhật danh sách hay không.

Tạo 1 danh sách và 2 action add/remove:

  val users = mutableListOf<Users>(
            Users(1, "User1", "Address 1", 0),
            Users(2, "User2", "Address 2", 1),
            Users(3, "User3", "Address 3", 2),
            Users(4, "User4", "Address 4", 3),
            Users(5, "User5", "Address 5", 4)
        )
        val userAdapter = UserAdapter(users)
        rcvListUser.adapter = userAdapter
        //much be new instance
        val newListUser = mutableListOf<Users>()
        newListUser.addAll(users)
        btnAddItem.setOnClickListener {
            val lastId = newListUser.last().id + 1
            newListUser.add(Users(lastId, "User$lastId", "Address $lastId", lastId))
            userAdapter.setUserList(newListUser)
        }
        btnRemoveItem.setOnClickListener {
            newListUser.removeAt(2)
            userAdapter.setUserList(newListUser)
        }

Bên trong UserAdapter

class UserAdapter(private val userList: MutableList<Users>) :
    RecyclerView.Adapter<UserAdapter.UserViewHolder>() {

    fun setUserList(updatedUserList: List<Users>) {
        val diffResult = DiffUtil.calculateDiff(UserDiffUtilCallback(userList, updatedUserList))
        userList.clear()
        userList.addAll(updatedUserList)
        diffResult.dispatchUpdatesTo(this)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = userList[position]
        holder.tvUserName.text = user.name
        holder.tvAddress.text = user.address
    }

    override fun getItemCount(): Int {
        return userList.size
    }

    inner class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val tvUserName = view.findViewById<TextView>(R.id.tvUserName)
        val tvAddress = view.findViewById<TextView>(R.id.tvAddress)

    }
}

Bên trong setUserList() phải là một danh sách mới.

dispatchUpdatesTo(this) gọi adapter và thông báo về Views được cập nhập.

Github sample

Nguyễn Linh

Chia sẻ để cùng tiến bộ...