[안드로이드] DiffUtil

J·2023년 3월 20일
1

안드로이드

목록 보기
23/29

1. 개요

DiffUtil은 안드로이드 어댑터에서 현재 데이터 리스트와 교체될 데이터 리스트를 비교하여 무엇이 바뀌었는지 알아내는 클래스입니다.

이를 통해 기존 데이터 리스트에서 아이템에 수정이 생겼을 때 전체 리스트를 갱신하는 게 아니라 바뀐 아이템에 대해서만 데이터를 바꿔주고, 이를 통해 빠르고 효율적으로 데이터 갱신을 할 수 있습니다.

2. 만드는 법

DiffUtil을 사용하기 위해선 DiffUtil.ItemCallback과 ListAdapter가 필요합니다.

이전에는 DiffUtil.Callback, AsyncListDiffer등이 필요했지만

이제는 ListAdapter라는 최강의 무기가 나왔기 때문에 위 두 개만 사용하면 됩니다.

3. DiffUtil.ItemCallback

non-null 인 데이터 리스트를 비교해 '차이'를 계산한 후 이를 콜백 해주는 클래스입니다.

여기서는 두 개의 추상 메서드를 구현해주어야 합니다.

a. areItemsTheSame

두 객체가 같은 아이템인지 비교합니다. 두 객체를 구별할 수 있는 값을 사용하여 비교합니다.

b. areContentsTheSame

위 메서드가 구별하는 것이 객체 자체라면, 이 메서드는 두 아이템이 같은 콘텐츠(내용물)를 갖고 있는지 비교합니다.

만약 두 객체의 ID나 객체를 식별할 수 있는 값이 같은데 안의 데이터가 다르다면 이 메서드에서 false를 반환합니다.

이 메서드는 areItemsTheSame 메서드가 true를 반환할 때에만 호출됩니다.

만일 false를 반환했다면 이 메서드가 호출되지 ㅇ낳고 그냥 바로 데이터를 갱신하면 되기 때문입니다.

4. ListAdapter

RecyclerView.Adapter를 상속받는 어댑터입니다.

특히 ListAdapter는 내부적으로 AsyncListDiffer를 갖고 있어 더 효율적으로 코드를 작성할 수 있습니다.

여기서 AsyncListDiffer란,
백그라운드 스레드에서 기존 데이터 리스트와 새로운 데이터 리스트의 '차이'를 비교하고 리스트를 갱신하는 역할을 하는 클래스입니다.

이전에는 이것도 개별적으로 생성해주어야 했지만, ListAdapter는 내부적으로 이를 갖고 있기에 이제는 생성해주지 않아도 됩니다.

이 클래스에는 다음과 같은 메서드가 있습니다.

a. getCurrentList()

어댑터가 갖고 잇는 리스트를 가져올 때 사용합니다.

b. submitList()

리스트 항목을 변경하고 싶을 때 사용합니다. 여기에 데이터를 변경할 리스트를 넣으면 알아서 리스트를 갱신해줍니다.

5. 사용법

1) 데이터 클래스 생성

Person 클래스라는 데이터 클래스를 생성해줍니다.

data class Person (var name: String, var age: Int, var num: Int)

2) DiffUtil.ItemCallback 클래스 생성

class PersonDiffItemCallback : DiffUtil.ItemCallback<Person>() {
	override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
    return oldItem == newItem
    }
    override fun areContentsTheSame(oldItem: Person, newItem: Person): Boolean {
    return oldItem == newItem
    }
}

이때 두 메서드에서는 원래 다르게 비교를 해야 합니다. areItemsTheSame은 객체 자체를 비교하고,
areContentsTheSame은 객체의 내용물을 비교하기 때문입니다.
그러나 저는 현재 그 둘의 차이를 둘 필요가 없기 때문에 두지 않았습니다.

만일 데이터베이스를 다룬다면 areItemsTheSame() 메서드에는 ID값을 비교하고,
areContentsTheSame() 메서드에서는 데이터를 비교하면 되겠습니다.

3) ListAdapter 클래스 생성

class PersonListAdapter : ListAdapter<Person, PersonListAdapter.Holder> (PersonDiffItemCallback()) {
	override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
    	val binding = PersonItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return Holder(binding)
    }
    override fun onBindViewHolder(holder: Holder, position: Int) {
    	holder.setPerson(currentList[position])
    }
    fun add() {
    	val newPersons = mutableListOf<Person>()
        newPersons.addAll(currentList)
        newPersons.add(Person("Java", 20, PersonAdapter.index++)) submitList(newPersons)
    }
    fun delete() {
    val newPersons = mutableListOf<Person>()
    newPersons.addAll(currentList)
    val index = (Random.nextDouble() * currentList.size).toInt()
    newPersons.removeAt(index) submitList(newPersons)
    }
    inner class Holder(val binding: PersonItemBinding) : RecyclerView.ViewHolder(binding.root) {
    fun setPerson(item: Person) {
    	binding.name.text = item.name binding.num.text = "${item.num}번째"
        }
    }
}

먼저 일반적인 리싸이클러뷰의 어댑터처럼 ViewHolder 클래스 및 필요한 메서드를 오버라이드해서 구현해줍니다.

그 이외의 메서드는 제가 임의로 작성한 코드이며, 내용은 다음과 같습니다.

a.add

아이템을 추가하는 메서드입니다.

이때 add() 메서드에서는 새로운 리스트를 생성한 다음, 기존 리스트를 복사한 후 데이터를 추가한 것을 볼 수 있습니다.

그리고 submitList() 메서드를 호출하는데, 이렇게 하는 이유는 다음과 같습니다.

DiffUtil은 기존 데이터 리스트와 새로운 데이터 리스트의 '차이'를 분석해서 해당 부분만 갱신을 해줍니다.

따라서 두개의 리ㅏ스트가 필요하며, 리스트를 갱신할 때에도 새로운 리스트를 전달해야 합니다.

그렇기에 새로운 리스트를 만든 다음 submitList() 메서드에 파라미터로 새로운 리스트를 전달하는 것입니다.

b. delete()

아이템을 제거하는 메서드입니다.

c. deleteRandom()

임의 인덱스에 위치한 아이템을 제거하는 메서드입니다.

0개의 댓글