정의
RecyclerView에 현재 보이고 있는 Item에 변경이 일어날 경우 notifyItemChanged()를 통해 변경을 알려 새로고침을 해야 한다.
하지만 이 메소드를 통한 변경 알림은 adapter가 새로운 item 인스턴스를 생성하기 때문에 비용이 많이 들게 된다.
이러한 비효율적인 작업을 줄이고 합리적인 Item 변경을 위해 DiffUtil이 개발되었다.
이 클래스는 이전 목록과 새로운 목록 간의 차이점을 찾고 업데이트가 필요한 목록을 찾아 반환하여 RecyclerView의 adapter에 업데이트를 알린다.
위 동작의 기반은 Eugene W.Myers의 차이 알고리즘을 적용하여 최소한의 업데이트 수를 계산한다.
사용법
DiffUtil.Callback은 추상 클래스로 위에서 언급한 두 목록 간의 차이를 계산하기 위해 DiffUtil에 의해 콜백 처리 클래스로 사용된다.
내부 구현은 4개의 추상 메소드와 1개의 비 추상 메소드가 있다.
public abstract static class Callback {
/**
* 이전 목록의 개수를 반환
*/
public abstract int getOldListSize();
/**
* 새로운 목록의 개수를 반환
*/
public abstract int getNewListSize();
/**
* 두 객체가 같은 항목인지에 대한 결정
*/
public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
/**
* 두 항목의 데이터가 같은지에 대한 결정
*/
public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
/**
* areItemsTheSame이 true고 areContentsTheSame가 false면 변경 내용에 대한 페이로드를 가져옴
*/
@Nullable
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return null;
}
}
각 메소드들이 어떤 역할을 하는지 자세하게 알아보자
1. getOldListSize()
- 이전 데이터의 크기를 반환.
2. getNewListSize()
- 새로운 데이터의 크기를 반환.
3. areItemsTheSame(int oldItemPosition, int newItemPosition)
- 두 아이템이 같은 객체인지 여부를 반환.
4. areContentsTheSame(int oldItemPosition, int newItemPosition)
- areItemTheSame() 이 true를 반환할 경우 두 객체가 동일한 데이터를 갖고 있는지에 대한 여부를 반환한다. areItemTheSame() 이 true일 경우 동작하는 이유는 애초에 객체가 다를 경우에 데이터 비교는 필요 없는 동작이기 때문이다.
5. getChangePayload(int oldItemPosition, int newItemPosition)
- areItemTheSame() 이 true를, areContentsTheSame() 이 false일 경우 객체는 동일하더라도 포함하는 데이터가 다르므로 새로운 데이터가 들어왔다는 것으로 판단하고 해당 메소드가 호출되며 변경 내용에 대한 데이터를 가져온다. 단, 이 메소드는 추상 메서드가 아니어서 필수적으로 오버라이드는 필요 없다.
DiffUtil.Callback을 구현하면 아래와 같다.
class Diff(
private val oldItems: List<Data>,
private val newItems: List<Data>
): DiffUtil.Callback(){
override fun getOldListSize(): Int = oldItems.size
override fun getNewListSize(): Int = newItems.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return oldItem == newItem
}
}
좀 더 간편하게 데이터에 대한 인덱싱과 차이점만으로 구현하고 싶다면 DiffUtil.ItemCallback()을 이용하면 된다.
public abstract static class ItemCallback<T> {
/**
* 두 객체가 같은 항목인지에 대한 결정
*/
public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem);
/**
* 두 항목의 데이터가 같은지에 대한 결정
*/
public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem);
/**
* areItemsTheSame이 true고 areContentsTheSame가 false면 변경 내용에 대한 페이로드를 가져옴
*/
@SuppressWarnings({"unused"})
@Nullable
public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) {
return null;
}
}
DiffUtil.ItemCallback() 추상 클래스를 구현하면 아래와 같을 것이다.
companion object DiffCallback : DiffUtil.ItemCallback<Data>() {
override fun areItemsTheSame(oldItem: Data, newItem: Data): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Data, newItem: Data): Boolean = oldItem == newItem
}
위 추상 클래스를 구현하고 기존에 Adapter를 구현할 때 상속하고 있던 RecyclerView.Adapter가 있다면 ListAdapter로 변경한다.
ListAdapter에 대한 구현 스펙은 아래와 같다.
ListAdapter<Data, RecyclerView.ViewHolder>(DiffCallback)
이렇게 ListAdapter로 변경까지 완성하면 더 이상 데이터를 Adapter에 넘겨주지 않더라도 submitList를 통해 보다 비용을 줄여 데이터 변경이 가능하다.
'Android' 카테고리의 다른 글
[Android] 안드로이드 스튜디오 GradleView Tasks 미노출시 (0) | 2021.12.28 |
---|---|
[Android] Gradient 배경 만들기 (0) | 2021.12.27 |
[Android] Context (0) | 2021.09.01 |
[Android] Serializable과 Parcelable (0) | 2021.08.05 |
[Android] Mac M1 Room 라이브러리 에러 (0) | 2021.07.30 |