DiffUtil로 RecyclerView Item을 쉽게 업데이트하기

Hyemdooly·2023년 6월 8일
0

데이터 변동 가능성이 있는 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?

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! 애용하도록 하자.

0개의 댓글