안드로이드

[Android] DiffUtil에 대해 알아보자

코딩하는후운 2024. 3. 20. 13:18
반응형

Android DiffUtil

리싸이클러뷰 아이템 구성이 바뀔때,

notifyDataSetChanged()로 모든 아이템을 업데이트 하는 방법을 사용해 왔다. 하지만 이 방법은 아이템 개수가 많아질수록 비효율적일 수 밖에 없다.

 

이런 문제를 알고 구글에서는 DiffUtil이라는 매우 편리한 유틸리티 클래스를 만들었다.

두 리스트의 차이점을 찾아 업데이트 되어야 할 목록을 반환 해 줘서 RecyclerView어댑터에 업데이트를 알리는데 사용

 

DiffUtil 사용하기

먼저 DiffUtil.Callback을 구현한 클래스를 만들어야 한다.

class PersonDiffCallback(
    private val oldList: List,

    private val newList: List

) : DiffUtil.Callback() {
    override fun getOldListSize() = oldList.size

    override fun getNewListSize() = newList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
        oldList[oldItemPosition].id == newList[newItemPosition].id

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
        oldList[oldItemPosition] == newList[newItemPosition]
}

이 Callback 클래스를 RecyclerView의 리스트 업데이트 하는 함수에 아래와 같이 코드를 추가한다.

class PersonDiffAdapter : RecyclerView.Adapter<PersonViewHolder>() {
    private val people = mutableListOf<Person>()

    ...

    fun replaceItems(newPeople: List<Person>) {
        val diffCallback = PersonDiffCallback(people, newPeople)
        val diffResult = DiffUtil.calculateDiff(diffCallback)

        people.clear()
        people.addAll(newPeople)

        diffResult.dispatchUpdatesTo(this)
    }
}

 

DiffUtil의 메서드의 역할은

1. calculateDiff()에서 diff 알고리즘을 통해 변경된 아이템을 감지하고,

2. dispatchUpdatesTo()에서 지정된 Adapter로 업데이트 이벤트를 전달한다.

 

  • DiffUtil은 dispatchUpdatesTo()에서 변경사항을 리스트에 적용할 때 추가 및 삭제 애니메이션을 제공한다.
  • DiffUtil은 아이템 개수가 많을 경우 calculateDiff()의 diff 계산 시간이 길어질 수 있기 때문에 백그라운드 스레드에서 처리하는 것이 좋다.
  • dispatchUpdatesTo()로 RecyclerView에 변경 사항을 적용하기 전에 원본 리스트를 새로운 리스트로 교체하는 작업을 해줘야 한다.

3번 부가 설명

그 전에 dispatchUpdatesTo()에서 하는 일을 알아야 한다.

dispatchUpdatesTo()에서는 diff 계산에서 반환된 DiffResult객체가 변경사항을 Adapter에 전달하고 Adapter가 변경 사항에 대해 알림을 받는다.

이 알림은 위에서 소개한 notify- 메서드로 변경 사항에 대해 리스트의 아이템이 업데이트 된다.

 

아래 코드는 DiffUtil에서 Adapter로 변경 사항을 notify 메서드로 알리는 부분이다.

DiffUtil에서 사용하는 notify- 메서드

DiffUtil 공식문서에는

리스트를 교체한 직후 업데이트 이벤트를 어댑터로 전송하고 그 이후에 RecyclerView가 리스트에 접근해야 하기 때문에 dispatchUpdatesTo()를 호출하기 전에 리스트를 변경해야 한다고 한다.

즉, dispatchUpdatesTo()에서는 데이터를 변경 할 때 notify- 메서드로 즉시 어댑터로 업데이트 이벤트를 전달하기 때문에 그 전에 새로운 리스트로 교체를 해야 RecyclerView에 제대로 적용이 된다고 함.

 

또한 어댑터에서 사용하는 리스트는 MutableList로 만들어야 제대로 동작한다.

 

이슈

  • DiffUtil사용했을 때 리스트가 변경되면서 recyclerView의 스크롤이 최상단으로 올라가는 이슈가 발생
  • areItemsTheSame 에서 다르다고 해버리니까, 해당 아이템이 제거된것으로 판단되어, 포커스를 잃어 스크롤이 된것 같습니다.
    아이템은 같고, 해당 내용이 변경 되었다 -> areContentsTheSame 에서 다르다고 해서 유지 할수 있는듯 합니다.
  • DiffUtil사용 했을 때 중간 아이템들이 사라지면서 스크롤이 유지되는 이슈도 발생(recyclerView.scrollPosition(0)으로 Refresh시에 최상단으로 이동 시켜주었다)

 

# 문제

childs: MutableList 라는 것으로 학생 리스트를 관리

 

DiffUtil로 childs와 newList비교 하였는데 헤더 부분이 계속 같은 데이터를 oldItems와 newItems가 같은 데이터를 바라본다.(변경된 값으로 똑같게 나온다ㅠ) 

(멤버변수의 데이터가 같은 주소값을 참조하는것인지...(?))

그래서 서버에서 값을 받아오면 childs의 리스트를 copy()하여 tempList에 담고 새로운 리스트값으로 넘겨주어 해결 하였다.

 

 

 

참조 : 

https://s2choco.tistory.com/33

 

반응형