이전 글에서 DiffUtil을 활용하여 RecyclerView Item을 쉽게 업데이트하는 방법을 소개했다.
사실 DiffUtil은 List Item의 양이 많아질 수록 계산하는데 더 많은 시간이 소요되기 때문에, 백그라운드 스레드에서 계산하도록 하는 것이 좋다. 계산은 백그라운드에서, 계산 결과는 UI 스레드에서 처리해야한다. 이전에 소개했던 방법은 모두 메인 스레드에서 처리하고 있다.
하지만 안드로이드에서는 개발자가 직접 처리하지 않아도 내부적으로 처리해주는 클래스가 존재한다. 하나씩 차근차근 살펴보자
Helper for computing the difference between two lists via DiffUtil on a background thread.
It can be connected to a RecyclerView.Adapter, and will signal the adapter of changes between sumbitted lists.
백그라운드 스레드에서 DiffUill을 통해 두 리스트 간의 차이를 계산하는 헬퍼다.
RecyclerView.Adapter에 연결할 수 있으며, 합산된 리스트들 사이의 변경 사항을 어댑터에게 알린다.
class UserAdapter : RecyclerView.Adapter<UserViewHolder>() {
private val differ: AsyncListDiffer<User> = AsyncListDiffer(this, DIFF_CALLBACK)
fun submitList(items: List<User>) {
differ.submitList(items)
}
// ...
companion object {
val DIFF_CALLBACK: DiffUtil.ItemCallback<User> = object : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldUser: User, newUser: User): Boolean {
// User properties may have changed if reloaded from the DB, but ID is fixed
return oldUser.getId() === newUser.getId()
}
override fun areContentsTheSame(oldUser: User, newUser: User): Boolean {
// NOTE: if you use equals, your object must properly override Object#equals()
// Incorrectly returning false here will result in too many animations.
return oldUser.equals(newUser)
}
}
}
}
AsyncListDiffer를 적용해보자. 코드는 공식 문서의 코드를 Kotlin으로 바꿔 가져왔다.
DiffUtil 콜백을 선언해주고 AsyncListDiffer 객체를 생성할 때 그 콜백을 넣어주면 된다. 리스트를 변경할 때는 differ.submitList(items)
함수를 사용하면 된다.
이전 코드보다 더 간단하게 리스트를 변경하고, 심지어 백그라운드에서 계산 과정을 처리하도록 할 수 있다.
RecyclerView.Adapter base class for presenting List data in a RecyclerView, including computing diffs between Lists on a background thread.
This class is a convenience wrapper around AsyncListDiffer that implements Adapter common default behavior for item access and counting.
백그라운드 스레드에서 리스트들 간 차이 계산을 포함한, RecyclerView에서 리스트 데이터를 표시하기 위한 RecyclerView.Adapter 베이스 클래스다.
이 클래스는 아이템 접근과 카운팅과 같은 공통 기본 동작을 구현한 AsyncListDiffer의 편의 래퍼이다.
하지만 여기서 더 간단하게! ListAdapter를 사용하면 AsyncListDiffer를 내부적으로 사용하게 할 수 있다.
지금 진행하고 있는 프로젝트의 코드를 가져왔다.
// DiffUtil
class RegionsDiffUtil : DiffUtil.ItemCallback<RegionSelectionModel>() {
override fun areItemsTheSame(
oldItem: RegionSelectionModel,
newItem: RegionSelectionModel,
): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: RegionSelectionModel, newItem: RegionSelectionModel): Boolean {
return oldItem == newItem
}
}
// ListAdapter
class FirstRegionsAdapter(private val onItemClickListener: (Long) -> Unit) :
ListAdapter<RegionSelectionModel, FirstRegionViewHolder>(RegionsDiffUtil()) {
fun setRegions(regions: List<RegionSelectionModel>) {
submitList(regions)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FirstRegionViewHolder {
return FirstRegionViewHolder.create(parent, onItemClickListener)
}
override fun onBindViewHolder(holder: FirstRegionViewHolder, position: Int) {
holder.bind(currentList[position]) // getItem(position: Int) 함수를 사용해도 OK
}
}
변수를 따로 두지 않고, ListAdapter를 상속받아 사용하면 된다. 개인적으로 분리하는 것을 좋아해서 DiffUtil도 다른 클래스로 분리했지만, 기존과 같이 companion object에 선언해도 상관 없다.
나는 리스트 아이템이 변경될 수 있는 경우라면 무조건 ListAdapter를 사용하고 있다. 기존 방식과 비교했을 때 보일러 플레이트 코드를 줄여주어 사용하지 않을 이유가 없기 때문이다.