DiffUtil, AsyncListDiffer, ListAdapter

boogi-woogi·2024년 1월 22일
0
post-thumbnail

notifyDataSetChanged()

  • notifyDataSetChanged()은 호출하면 requestLayout()을 통해 새롭게 View를 그리면서 공간을 재할당받기 때문에 비용이 큰 작업이다.

DiffUtil

  • RecyclerView에 존재하는 기존 아이템(oldList)들과 새로운 아이템(newList)들의 차이를 계산해서 반영하는 유틸리티 클래스이다.
  • Eugene W. Myers 알고리즘을 사용하여 최소 업데이트 횟수를 계산한다.
  • 사용을 위해서는 DiffUtil.Callback()을 상속받은 클래스를 구현해야한다.
class MatchDiffUtilCallBack(
    private val oldMatches: List<MatchUiModel>,
    private val newMatches: List<MatchUiModel>
) : DiffUtil.Callback() {

    override fun getOldListSize(): Int = oldMatches.size

    override fun getNewListSize(): Int = newMatches.size

/**
* 일반적으로 객체의 id를 가지고 판단한다.
*/
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
        oldMatches[oldItemPosition] == newMatches[newItemPosition]

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
        oldMatches[oldItemPosition] == newMatches[newItemPosition]
}
  • areItemsTheSame(): 두 항목이 같은 객체인지 반환한다.
  • areContentsTheSame(): 두 항목의 데이터가 같은지 여부를 결정한다. areItemsTheSame()이 true인 경우에만 호출된다.
/**
* DiffUtil을 적용한 RecyclerView.Adapter
*/
class RecentMatchAdapter2(private var matches: List<MatchUiModel>) : RecyclerView.Adapter<MatchViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MatchViewHolder {
        return MatchViewHolder.valueOf(parent)
    }

    override fun getItemCount(): Int = matches.size

    override fun onBindViewHolder(holder: MatchViewHolder, position: Int) {
        holder.bind(matches[position])
    }

    fun updateMatches(matches: List<MatchUiModel>) {
        val diffCallback = MatchDiffUtilCallBack(this.matches, matches)
        val diffResult: DiffUtil.DiffResult = DiffUtil.calculateDiff(diffCallback)
        this.matches = matches

        diffResult.dispatchUpdatesTo(this)
    }
}
  • 데이터가 많아질수록 알고리즘의 시간복잡도가 커지기 때문에 백그라운드 스레드에서 처리하는 것이 좋다.

AsyncListDiffer

  • AsyncListDiff는 백그라운드 스레드에서 리스트의 변경사항을 계산하고 업데이트까지 진행을 시켜준다.
  • DiffUtil.ItemCallback를 구현하는 클래스를 만들어야 한다.
  class MatchDiffUtilCallBack : DiffUtil.ItemCallback<MatchUiModel>() {

    override fun areItemsTheSame(
        oldItem: MatchUiModel,
        newItem: MatchUiModel
    ): Boolean = oldItem == newItem

    override fun areContentsTheSame(
        oldItem: MatchUiModel,
        newItem: MatchUiModel
    ): Boolean = oldItem == newItem
}
  • areItemsTheSame(): 두 항목이 같은 객체인지 반환한다.
  • areContentsTheSame(): 두 항목의 데이터가 같은지 여부를 결정한다. areItemsTheSame()이 true인 경우에만 호출된다.
  • AsyncListDiffer는 newList와 oldList의 참조가 같은 경우 변경사항을 체크하지도 않고 종료한다.
    • 새로운 데이터를 대입할 때 새로운 리스트를 생성한 후 submitList()를 하면 문제가 되지 않지만, 기존의 리스트를 수정해 submitList()를 하게 되면 같은 참조값으로 인해 리스트가 업데이트 되지 않는 버그가 발생한다.
      public void submitList(@Nullable final List<T> newList,
            @Nullable final Runnable commitCallback) {
        // incrementing generation means any currently-running diffs are discarded when they finish
        final int runGeneration = ++mMaxScheduledGeneration;

        if (newList == mList) {
            // nothing to do (Note - still had to inc generation, since may have ongoing work)
            if (commitCallback != null) {
                commitCallback.run();
            }
            return;
        }
        
        ...
      
       
   }
/**
* AsyncListDiffer를 적용한 RecyclerView.Adapter
*/
  class RecentMatchAdapter2(private var matches: List<MatchUiModel>) : RecyclerView.Adapter<MatchViewHolder>() {

    private val asyncDiffer = AsyncListDiffer(this, MatchDiffUtilCallBack())
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MatchViewHolder {
        return MatchViewHolder.valueOf(parent)
    }

    override fun getItemCount(): Int = matches.size

    override fun onBindViewHolder(holder: MatchViewHolder, position: Int) {
        holder.bind(matches[position])
    }

    fun updateMatches(matches: List<MatchUiModel>) {
        asyncDiffer.submitList(matches)
    }
}

ListAdapter

  • AsnycListDiffer를 내부적으로 사용하는 클래스이다.
    • private val asyncDiffer = AsyncListDiffer(this, MatchDiffUtilCallBack()) 다음과 같은 코드를 작성할 필요가 없다.
  • AsyncListDiffer와 마찬가지로 DiffUtil.ItemCallBack 클래스를 사용한다.
/**
* ListAdapter
*/
  class RecentMatchAdapter : ListAdapter<MatchUiModel, MatchViewHolder>(MatchDiffUtilCallBack()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MatchViewHolder {
        return MatchViewHolder.valueOf(parent)
    }

    override fun onBindViewHolder(holder: MatchViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    override fun submitList(list: List<MatchUiModel>?) {
        super.submitList(list)
    }
}
profile
https://github.com/boogi-woogi

0개의 댓글