RecyclerView의 notifyDataSetChanged와 DiffUtil

blue·2021년 4월 29일
3

android

목록 보기
2/4
post-thumbnail

나는 b를 삭제했는데 c가 삭제된 것처럼 보이는 매직

나는 b를 지웠는데 c가 삭제된 것처럼 보인다. 하지만 나갔다 들어오니 b가 지워진게 맞다;

코드는 아이템이 변경되었을 때 notifyDataSetChanged()를 호출하여 UI가 갱신되도록 구현하였다.
하지만 해당 함수 사용이 부적절함을 깨달았다.

Recylcerview.Adapter

리사이클러뷰는 어댑터의 메소드를 통해 아이템 변경을 감지하고 갱신할 수 있다.
그러나 정확히 어떤 아이템이 변경되었는지는 알 수 없기 때문에 변경된 아이템의 position을 알려줘야 한다.

  1. notifyDataSetChanged
    아이템 변경(데이터가 업데이트 되었지만 위치는 변하지 않았을 때), 구조적 변경(아이템간에 삽입, 삭제, 이동이 일어났을 때)에 사용한다.

  2. notifyItemChanged
    notifyItemChanged(int position, Object payload)
    position 위치의 아이템이 변경되었다고 파라미터를 통해 알려줄 수 있다.

  3. notifyItemInserted
    notifyItemInserted(int position)
    position 위치에 아이템이 추가되었다는 뜻이다.

  4. notifyItemMoved
    notifyItemMoved(int fromPostion, int toPosition)
    인덱스 fromPosition 아이템이 toPosition으로 이동하였다.

  5. notifyItemRangeChanged
    notifyItemRangeChanged(int positionStart, int itemCount, Object payload)
    positionStart부터 itemCount개까지 범위에서 변경이 일어났다.


5가지 방법에서 보듯이, 변경된 위치를 알려주거나 아니면 notifyDataSetChanged()로 전체 아이템을 다시 바인딩하는 방식이 있다. 아마 리스트에서 뭔가 삭제된 것 같긴 한데 무엇이 그러한지 알지 못해서 마지막 것을 그냥 지운 것 같다.

이를 해결하기 위해

temporalList.addAll(adapterList); // 임시 리스트에 Adapter에 연결한 list의 내용을 모두 담고
temporalList.add(newItem); // 새로운 아이템을 등록한 후에
adapterList.clear(); // adapter에 연결한 list의 내용을 모두 지웠다가
adapterList.addAll(temporalList); // 임시리스트의 내용을 모두 adapter에 연결한 리스트에 추가하기
adapter.notifyDataSetChanged(); // 그리고 notifyDataSetChanged를 호출해보세요.

이런 방법도 있는 듯 하나 효율적으로 보이지 않는다.
1000개 중 하나만 변경되었더라도 전부 다 지웠다가 다시 add해야 하기 때문이다.

그래서 아이템 변경을 감지하고 갱신하는 역할을 DiffUtil에게 위임하여 문제점을 해결하였다.

DiffUtil

DiffUtil은 oldList와 newList를 비교하여 차이를 계산하고, newList로 갱신해주는 유틸리티 클래스이다.


즉, 이 클래스를 사용하면 아이템 변경의 구체적인 상황에 따라 Adapter의 적절한 메소드를 호출하지 않아도 된다.

class ContactDiffUtil(private val oldList: List<Contact>, private val currentList: List<Contact>):DiffUtil.Callback(){
    override fun getOldListSize(): Int =oldList.size

    override fun getNewListSize(): Int =currentList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition].id==currentList[newItemPosition].id
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition]==currentList[newItemPosition]
    }

}

총 4개 메소드를 오버라이드 해줘야 한다. 메소드는 이름명과 리턴값을 보면 어떤 역할을 하는지 쉽게 예측할 수 있다.

위와 같이 클래스를 하나 정의하고, 원래 notifySetDataChanged()를 호출할 곳에 아래 코드로 대체하면 된다.

fun setContact(contacts: List<Contact>){
        val diffResult=DiffUtil.calculateDiff(ContactDiffUtil(this.contacts, contacts), false)
        diffResult.dispatchUpdatesTo(this)
        this.contacts=contacts
    }

코드의 뜻은,
1. calculateDiff()로 oldList와 newList의 차이를 계산한다.
2. 차이 값을 업데이트하고, (notify~ 기능와 같다고 보면 된다).
3. list가 갱신되었으므로 기존 this.contacts를 newList인 contacts로 업데이트한다.

참고
profile
😎

0개의 댓글