이전 글에서 DiffUtil
을 이용하여 RecyclerView
를 update하는 방법을 알아보았습니다. 이번에는 DiffUtil.calculateDiff
를 이용하여 background thread에서 차이를 계산하고 main thread에서 RecyclerView
를 update시켜주는 AsyncListDiffer
와 wrapper 클래스인 ListAdapter
에 대해 알아보겠습니다.
public abstract class ListAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
final AsyncListDiffer<T> mDiffer;
...
@SuppressWarnings("unused")
protected ListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
new AsyncDifferConfig.Builder<>(diffCallback).build());
mDiffer.addListListener(mListener);
}
...
}
ListAdapter
은 RecyclerView.Adapter
를 상속받습니다. 사용자가 정의한DiffUtil.ItemCallback
을 이용하여 AsyncListDiffer
객체를 생성하여 field로 가지고 있는것을 볼 수 있습니다.
Helper for computing the difference between two lists via
DiffUtil
on a background thread. [1]
Android developers 공식 사이트에서 background thread에서 DiffUtil
을 사용하여 두 리스트간의 차이를 계산해주는 helper 클래스라고 정의 되어 있습니다.
RecyclerView
를 update하기 위해서 ListAdapter
객체의 submitList
method를 호출하면 내부에 존재하는 AsyncListDiffer
의 submitList
로 delegate합니다. 그렇게 되면 필요시 AdapterListUpdateCallback
객체를 이용하여 RecyclerView
를 update하게 됩니다.
// AsyncListDiffer.java
// fast simple first insert
if (mList == null) {
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
// notify last, after list is updated
mUpdateCallback.onInserted(0, newList.size()); // 모든 item을 추가
onCurrentListChanged(previousList, commitCallback);
return;
}
// AdapterListUpdateCallback.java
@Override
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
이전에 submitList
로 null을 넘겨주었거나 혹은 처음 리스트를 넘겨줄 경우에 onInserted
를 이용하여 새로운 아이템들을 추가해줍니다.
// AsyncListDiffer.java
// fast simple remove all
if (newList == null) {
//noinspection ConstantConditions
int countRemoved = mList.size();
mList = null;
mReadOnlyList = Collections.emptyList();
// notify last, after list is updated
mUpdateCallback.onRemoved(0, countRemoved); // 모든 item을 삭제
onCurrentListChanged(previousList, commitCallback);
return;
}
// AdapterListUpdateCallback.java
@Override
public void onRemoved(int position, int count) {
mAdapter.notifyItemRangeRemoved(position, count);
}
submitList
로 null을 넘겨줄 경우 onRemoved
를 이용하여 이전의 아이템들을 삭제합니다.
// AsyncListDiffer.java
final List<T> oldList = mList;
// background thread에서 calculateDiff를 수행
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { ... });
// 차이를 계산하여 main thread에서 update
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
latchList(newList, result, commitCallback);
}
}
});
}
});
@SuppressWarnings("WeakerAccess") /* synthetic access */
void latchList(
@NonNull List<T> newList,
@NonNull DiffUtil.DiffResult diffResult,
@Nullable Runnable commitCallback) {
final List<T> previousList = mReadOnlyList;
mList = newList;
// notify last, after list is updated
mReadOnlyList = Collections.unmodifiableList(newList);
diffResult.dispatchUpdatesTo(mUpdateCallback);
onCurrentListChanged(previousList, commitCallback);
}
AsyncDifferConfig
에서 background thread executor를 가져와서 background thread에서 DiffUtil.calculateDiff
를 이용하여 두 리스트의 차이를 계산합니다. 계산시 필요한 DiffUtil.Callback
객체를 사용자가 정의하여 넘겨준 ItemCallBack
을 이용하여 생성합니다.
계산 후 Main thread에서 결과를 dispatchUpdatesTo
에 넘겨줌으로써 RecyclerView
를 update하게 됩니다.
if (newList == mList) {
// nothing to do (Note - still had to inc generation, since may have ongoing work)
if (commitCallback != null) {
commitCallback.run();
}
return;
}
같은 리스트 객체를 전달하게 되면 아무런 동작을 하지 않게 되므로 제대로 update하기 위해서 항상 새로운 리스트 객체를 전달해주어야 합니다.
[1] "AsyncListDiffer," Android Developers, last modified May 5, 2021, accessed Apr 26, 2022, https://developer.android.com/reference/kotlin/androidx/recyclerview/widget/AsyncListDiffer.