12/08/2018, 17:11

Cập nhật Recyclerview với DiffUtil

Tiếp theo chủ đề về RecyclerView, bài này mình xin nói về việc cập nhật lại RecyclerView, việc này thường được làm bằng cách lấy dữ liệu từ máy chủ sau đó cập nhật lại các mục mới nhận được. Nếu có delay nhỏ, nó sẽ ảnh hưởng lớn đến trải nghiệm người dùng, vậy nên chúng ta cần việc cập nhật càng ...

Tiếp theo chủ đề về RecyclerView, bài này mình xin nói về việc cập nhật lại RecyclerView, việc này thường được làm bằng cách lấy dữ liệu từ máy chủ sau đó cập nhật lại các mục mới nhận được. Nếu có delay nhỏ, nó sẽ ảnh hưởng lớn đến trải nghiệm người dùng, vậy nên chúng ta cần việc cập nhật càng nhanh càng tốt và tốn càng ít tài nguyên máy càng tốt. Thông thường, khi muốn cập nhật lại một RecyclerView, chúng ta sẽ gọi notifyDataSetChanged(). Nhưng vấn đề là ta sẽ phải cập nhật lại toàn bộ các item trong list, nhiều vòng lặp phải thực hiện khi gọi hàm này. Và DiffUtil đã được sinh ra để giải quyết điều này.

Đến bản 24.2.0, RecyclerView support library, v7 package cung cấp class được gọi là DiffUtil. Về cơ bản class này tìm điểm khác nhau giữa 2 list và cung cấp output là list cập nhật, nó được sử dụng để thông báo cập nhật cho một adapter của RecyclerView. Nó sử dụng thuật toán của Eugene W. Myers để tính số cập nhật tối thiểu nên tài nguyên sử dụng là ít nhất.

DiffUtil tính toán sự khác nhau giữa 2 list và trả về trong DiffUtil.Callback.

Bạn cần extend và override các methods:

getOldListSize() – Trả về size của list cũ

getNewListSize() – Trả về size của list mới

areItemsTheSame(int oldItemPosition, int newItemPosition)

– Nó quyết định 2 object có cùng đại diện cho 1 item hay không?

areContentsTheSame(int oldItemPosition, int newItemPosition)

– Nó quyết định 2 item có data giống nhau hay không. Method này chỉ được gọi khi areContentsTheSame() = true.

getChangePayload(int oldItemPosition, int newItemPosition)

– Nếu areItemTheSame() = true và areContentsTheSame() = false, DiffUtil gọi method này để trả về sự thay đổi.

Dưới đây là một ví dụ: class Employee được dùng trong EmployeeRecyclerViewAdapter và EmployeeDiffCallback để sắp xếp một danh sách nhân viên.

public class Employee {
    public int id;
    public String name;
    public String role;
}

Đây là việc thực hiện class Diff.Callback .

Bạn có thể thấy rằng getChangePayload() không phải là một abstract method.

public class EmployeeDiffCallback extends DiffUtil.Callback {

    private final List<Employee> mOldEmployeeList;
    private final List<Employee> mNewEmployeeList;

    public EmployeeDiffCallback(List<Employee> oldEmployeeList, List<Employee> newEmployeeList) {
        this.mOldEmployeeList = oldEmployeeList;
        this.mNewEmployeeList = newEmployeeList;
    }

    @Override
    public int getOldListSize() {
        return mOldEmployeeList.size();
    }

    @Override
    public int getNewListSize() {
        return mNewEmployeeList.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return mOldEmployeeList.get(oldItemPosition).getId() == mNewEmployeeList.get(
                newItemPosition).getId();
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        final Employee oldEmployee = mOldEmployeeList.get(oldItemPosition);
        final Employee newEmployee = mNewEmployeeList.get(newItemPosition);

        return oldEmployee.getName().equals(newEmployee.getName());
    }

    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        // Implement method if you're going to use ItemAnimator
        return super.getChangePayload(oldItemPosition, newItemPosition);
    }
}

Sau khi hoàn thành DiffUtil.Callback , bạn phải cập nhật thay đổi danh sách trong RecyclerViewAdapter như dưới đây:

public class CustomRecyclerViewAdapter extends RecyclerView.Adapter<CustomRecyclerViewAdapter.ViewHolder> {

  ...
       public void updateEmployeeListItems(List<Employee> employees) {
        final EmployeeDiffCallback diffCallback = new EmployeeDiffCallback(this.mEmployees, employees);
        final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);

        this.mEmployees.clear();
        this.mEmployees.addAll(employees);
        diffResult.dispatchUpdatesTo(this);
    }
}

Gọi dispatchUpdatesTo(RecyclerView.Adapter)để gửi danh sách cập nhật.

DiffResult object được trả về và gửi các thay đổi cho Adapter.

Object được trả về trong getChangePayload () được gửi đi từ DiffResult bằng cách sử dụng notifyItemRangeChanged(position, count, payload)

Được gọi là methodonBindViewHolder(… List<Object> payloads) .

@Override
public void onBindViewHolder(ProductViewHolder holder, int position, List<Object> payloads) {
// Handle the payload
}

DiffUtil cũng sử dụng các method của RecyclerView.Adapter để thông báo cho adapter cập nhật dư liệu như

notifyItemMoved()

notifyItemRangeChanged()

notifyItemRangeInserted()

notifyItemRangeRemoved()

Bạn có thể tìm hiểu thêm về các method này tại đây

Nếu như danh sách của bạn lớn, bạn nên chạy DiffUtil trên background thread vì nó có thể chiếm một khoảng thời gian. Sau đó get DiffUtil.DiffResult, apply nó vào recyclerview của bạn trên main thread. Ngoài ra max size của list có thể thực hiện là 2²⁶.

Thuật toán này được tối ưu hóa cho không gian và sử dụng không gian O (N) để tìm số lượng tối thiểu các bổ sung và loại bỏ giữa hai list mới và cũ. Nó có O (N + D ^ 2) thời gian dự kiến thực hiện, trong đó D là chiều dài của đoạn thay đổi.

Nếu move detection được enabled, phải mất thêm O (N ^ 2) thời gian trong đó N là tổng số các mục được thêm vào và loại bỏ.

Nếu list của bạn đã được sắp xếp từ trước theo cùng một ràng buộc (vd: create time của danh sách các bài đăng) thì bạn không nên enabled move detection, nhằm cải thiện performance.

Chi tiết các bạn có thể tham khảo tại đây

Cảm ơn các bạn đã đọc bài, chúc các bạn code vui.

Nguồn:

https://developer.android.com/reference/android/support/v7/util/DiffUtil.html

https://medium.com/mindorks/diffutils-improving-performance-of-recyclerview-102b254a9e4a

https://android.jlelse.eu/smart-way-to-update-recyclerview-using-diffutil-345941a160e0

0