[Android] AsyncListDiffer와 ListAdapter

ErroredPasta·2022년 4월 19일
0

Android

목록 보기
3/5

이전 글에서 DiffUtil을 이용하여 RecyclerView를 update하는 방법을 알아보았습니다. 이번에는 DiffUtil.calculateDiff를 이용하여 background thread에서 차이를 계산하고 main thread에서 RecyclerView를 update시켜주는 AsyncListDiffer와 wrapper 클래스인 ListAdapter에 대해 알아보겠습니다.

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);
    }
    
    ...
    
}   

ListAdapterRecyclerView.Adapter를 상속받습니다. 사용자가 정의한DiffUtil.ItemCallback을 이용하여 AsyncListDiffer 객체를 생성하여 field로 가지고 있는것을 볼 수 있습니다.

AsyncListDiffer

Helper for computing the difference between two lists via DiffUtil on a background thread. [1]

Android developers 공식 사이트에서 background thread에서 DiffUtil을 사용하여 두 리스트간의 차이를 계산해주는 helper 클래스라고 정의 되어 있습니다.

submitList

RecyclerView를 update하기 위해서 ListAdapter객체의 submitList method를 호출하면 내부에 존재하는 AsyncListDiffersubmitList로 delegate합니다. 그렇게 되면 필요시 AdapterListUpdateCallback 객체를 이용하여 RecyclerView를 update하게 됩니다.

이전 리스트가 null일 경우

// 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를 이용하여 새로운 아이템들을 추가해줍니다.

리스트로 null을 전달

// 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하기 위해서 항상 새로운 리스트 객체를 전달해주어야 합니다.

Reference

[1] "AsyncListDiffer," Android Developers, last modified May 5, 2021, accessed Apr 26, 2022, https://developer.android.com/reference/kotlin/androidx/recyclerview/widget/AsyncListDiffer.

profile
Hola, Mundo

0개의 댓글