RecyclerView 효율적으로 사용 (DiffUtil, ListAdapter)

jeunguri·2022년 10월 26일
0

android

목록 보기
9/13


예를 들어 RecyclerView에서 보여줄 리스트에서 10개의 노래 아이템이 들어있는 리스트를 새로고침 했을 때, 9개의 데이터는 그대로인데 1개만 노래 제목이 바뀌었다면 어떻게 될까? notifyDataSetChanged() 를 사용하면 되겠지만, 실질적으로 다시 갱신해야 하는 아이템은 1개뿐인데 어댑터는 그 사실을 알지 못하므로 10개의 아이템을 모두 업데이트하게 되는 불필요한 비용이 발생하게 된다.

DiffUtil

DiffUtil은 이전 데이터 상태와 현재 데이터 간의 상태 차이을 계산하고, 반드시 업데이트해야 할 최소한의 데이터에 대해서만 갱신하게 된다. 즉, 데이터 업데이트 횟수를 최소한으로 가져가는 것이다.

원래의 리스트와 새로 들어온 리스트 간의 차이를 계산하기 위해 DiffUtil.Callback을 구현해야 한다.

메서드

DiffUtil.Callback은 4개의 추상 메서드와 1개의 일반 메서드로 이루어져 있다.

  • getOldListSize()
    이전(원래) 리스트의 크기를 반환

  • getNewListSize()
    새로 들어온 리스트의 크기를 반환

  • areItemsTheSame(int oldItemPosition, int newItemPosition)
    비교 대상인 두 아이템이 같은 객체인지 확인

  • areContentsTheSame(int oldItemPosition, int newItemPosition)
    두 아이템이 동일한 데이터를 가지는지 여부를 확인, areItemsTheSame()이 true를 반환될 때만 호출

  • getChangePayload(int oldItemPosition, int newItemPosition)
    areItemsTheSame()이 true로 반환되고, areContentsTheSame()가 false를 반환했다면, 새로운 데이터가 들어왔다는 것으로 간주하고 해당 메서드가 호출되어 변경 내용에 대한 페이로드를 가져온다.
    (해당 메서드는 추상 메서드가 아니기 때문에 필수로 오버라이드할 필요가 없다.)

사용 방법

  1. DiffUtil.Callback을 상속받는 콜백 클래스를 만들어주고, 비교 대상을 지정해준다.

  2. updateList() 함수를 통해 새로 들어온 데이터를 집어 넣게 되면, DiffUtil.calculateDiff에 해당 데이터를 집어넣게 되고 인자로는 구현한 콜백 클래스 객체를 전달한다.

  3. diffResult.dispatchUpdatesTo를 호출하게 되면, 최소한의 업데이트 연산으로 RecyclerView를 갱신한다.

class DiffUtilCallback(private val oldList: List<Any>, private val newList: List<Any>) :
    DiffUtil.Callback() {
    override fun getOldListSize(): Int = oldList.size

    override fun getNewListSize(): Int = newList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val oldItem = oldList[oldItemPosition]
        val newItem = newList[newItemPosition]

        return if (oldItem is Person && newItem is Person) {
            oldItem.id == newItem.id
        } else {
            false
        }
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
        oldList[oldItemPosition] == newList[newItemPosition]
}
fun updateList(items: List<Music>?) {
    items?.let {
        val diffCallback = DiffUtilCallback(this.items, items)
        val diffResult = DiffUtil.calculateDiff(diffCallback)

        this.items.run {
            clear()
            addAll(items)
            diffResult.dispatchUpdatesTo(this@Adapter)
        }
    }
}


ListAdapter

ListAdpater는 AsyncListDiffer를 더 쓰기 편하도록 랩핑한 클래스로 RecyclerView 어댑터를 만들 때 ListAdpater를 상속하도록 하면 된다.

메서드

  • getCurrentList()
    현재 리스트를 반환
  • onCurrentListChanged()
    리스트가 업데이트 되었을 때 실행할 콜백 지정
  • submitList(MutableList list)
    리스트 데이터를 교체할 때 사용


사용방법

class ModelRecyclerAdpater : ListAdapter<Model, ModelViewHolder>(Model.DiffUtilCallback()) {
	
	override fun onBindViewHolder(holder: ModelViewHolder, position: Int) {
		holder.bind(getItem(position))
	}

	override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ModelViewHolder(
		viewHolderModelBinding.inflate(LayoutInflater.from(parent.context), parent, false)
	)
 }
val adapter = ModelRecyclerAdapter
adapter.submitList(newItems)



0개의 댓글