데이터 변동 가능성이 있는 RecyclerView를 만들다보면, notifyDataSetChanged
를 자주 마주치게 된다. 하지만 이 함수는 아이템 뷰를 아예 다시 그리기 때문에 지양해야한다. 대신 notifyItemxxx
을 어떤 아이템이 변했는지 판단해서 ‘개발자가’ 일일히 호출해줄 수 있다.
if (original.size < items.size) { // 더보기 클릭한 경우
notifyItemRemoved(original.size - 1)
notifyItemRangeInserted(original.size - 1, items.size - original.size + 1)
return
}
val updatedItems = (newItems - original.toSet()).toList()
for (item in updatedItems) {
notifyItemChanged(items.indexOf(item))
}
개발자가 일일히 계산해서 로직을 짜야한다니… 마음 같아서는 그냥 notifyDataSetChanged
쓰고 싶다.
이러한 귀차니즘 개발자들을 위해 나온 정말정말 편한 클래스가 존재한다..!!!
DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list into the second one.
DiffUtil은 두 리스트 간의 차이를 계산하고 첫 번째 리스트를 두 번째 리스트로 변환하는 업데이트 작업 목록을 출력하는 유틸리티 클래스입니다.
두 리스트의 차이를 계산해주는 클래스이다. 콜백 클래스를 오버라이딩해서 정의만 해주면 바로 사용할 수 있다.
class ProductListDiffCallback(
private val oldList: List<ProductListViewItem>,
private val newList: List<ProductListViewItem>
) : DiffUtil.Callback() {
override fun getOldListSize(): Int {
// 기존 리스트의 크기 반환
return oldList.size
}
override fun getNewListSize(): Int {
// 새 리스트의 크기 반환
return newList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
// 두 아이템이 같은 객체인가? 반환
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return oldItem.type == newItem.type
// ProductListViewItem이 프로퍼티로 type을 가지고 있어서 이 둘을 비교했다.
// 그런 프로퍼티가 없다면 같은 클래스인지 판단하면 될 것.
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
// 같은 데이터를 갖고 있는가? 반환
// areItemsTheSame이 true이면 이 함수가 호출된다.
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
if (oldItem is ProductListViewItem.ProductItem && newItem is ProductListViewItem.ProductItem) {
return oldItem.product == newItem.product
}
if (oldItem is ProductListViewItem.RecentViewedItem && newItem is ProductListViewItem.RecentViewedItem) {
return oldItem.products == newItem.products
}
return oldItem is ProductListViewItem.ShowMoreItem && newItem is ProductListViewItem.ShowMoreItem
}
}
이 4가지 추상 메소드 외에 이미 정의되어 있는 getChangePayload(int oldItem, int newItem)
이라는 메소드도 있다. 이 메소드는 areItemTheSame()
이 true고, areContentsTheSame()
이 false면 새로운 아이템이 들어왔구나! 판단하고 이 메소드가 호출된다.
// In Adapter..
fun updateItems(newItems: List<ProductListViewItem>) {
val diffCallback = ProductListDiffCallback(items, newItems)
val diffResult = DiffUtil.calculateDiff(diffCallback) // 계산
items.clear()
items.addAll(newItems) // 새 리스트 넣어주기
diffResult.dispatchUpdatesTo(this) // 리사이클러뷰 갱신!
}
그리고 Activity에서 이 updateItems(newItems: List<ProductListViewItem>)
함수를 호출해주면, 알아서 새 데이터로 갈아끼워주고 최소한의 데이터에 대해서만 갱신한다. 이제는 notifyxxx
함수 안녕~
사용이 어렵지 않은 DiffUtil
! 애용하도록 하자.