RecyclerView에 표현할 데이터를 업데이트하기 위해 주로 notifyDataSetChanged()를 호출한다.
notifyDataSetChanged()
리스트의 내용이 변경되어 notifyDataSetChanged()를 호출하면,
Adapter에게 RecyclerView의 데이터가 바뀌었으니 모든 항목을 통째로 업데이트를 하라는 신호를 보낸다.
이 방법은 모든 데이터를 다시 그리기 때문에 업데이트 과정이 지연되어 UX에 영향을 미칠 가능성이 매우 크다.
우리는 리스트를 매일 보고 사용한다.
변경이 된 데이터에 대해서만 Adapter 업데이트를 할 필요가 있는데 이를 위해 고안된 것이 DiffUtil 클래스다.
DiffUtil을 이용하여 데이터를 효율적으로 업데이트 할 수 있다.
DiffUtil
RecyclerView의 성능 향상을 위해 사용하는 DiffUtil은 old list와 new list를 비교하여 어떤 것이 다른지 알아낸다.
이 클래스는 Eugene W. Myers's difference algorithm을 이용하여 최소한의 업데이트 수를 계산한다.
DiffUtil이 어떤 것이 변경되었는지 알아내면 RecyclerView는 이 정보를 사용하여 변경, 추가, 제거 또는 이동된 항목만 업데이트 한다.
DiffUtil을 사용하는 방법은 다음과 같다. 먼저 DiffUtil.Callback을 구현한 클래스를 만들어야 한다.
class UserDiffCallback(
private val oldList: List<User>,
private val newList: List<User>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].id == newList[newItemPosition].id
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition]
}
DiffUtil.Callback 구현이 완료되면 RecyclerView.Adapter의 리스트 업데이트 하는 함수에 추가한다.
class UserDiffAdapter : RecyclerView.Adapter<UserViewHolder>() {
private val user = mutableListOf<User>()
...
fun replaceItems(newUser: List<User>) {
val diffCallback = UserDiffCallback(user, newUser)
val diffResult = DiffUtil.calculateDiff(diffCallback)
user.clear()
user.addAll(newUser)
diffResult.dispatchUpdatesTo(this)
}
}
calculateDiff()에서 Diff 알고리즘을 통해 변경된 아이템을 감지한다.
Diff 계산에서 반환된 DiffResult 객체가 dispatchUpdatesTo()를 통해 Adapter로 업데이트 이벤트를 전달한다.
목록이 크면 이 작업에 상당한 시간이 걸릴 수 있으므로 백그라운드 스레드에서 이 작업을 실행하고 DiffUtil.DiffResult를 가져온 다음 기본 스레드의 RecyclerView에 적용하는 것이 좋다.
AsyncListDiffer
DiffUtil은 아이템 수가 많으면 연산에 필요한 시간이 길어질 수 있기 때문에 백그라운드 스레드에서 처리하는 것이 좋다.
AsyncListDiffer은 내부적으로 diff 계산을 백그라운드 스레드로 처리한 뒤 리스트 업데이트까지 해 준다.
덕분에 우리는 스레드를 신경쓰지 않고 DiffUtil을 훨씬 편하게 사용할 수 있다.
먼저 DiffUtil.ItemCallback 클래스를 만든다.
class UserDiffItemCallback : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: User, newItem: User) =
oldItem == newItem
}
그다음 RecyclerView Adapter에서 AsyncListDiffer를 생성해 사용하면 된다.
class UserAsyncDifferAdapter : RecyclerView.Adapter<UserViewHolder>() {
private val asyncDiffer = AsyncListDiffer(this, UserDiffItemCallback())
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = UserViewHolder(
ItemUserBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: UserViewHolder, position: Int) =
holder.bind(asyncDiffer.currentList[position])
override fun getItemCount() = asyncDiffer.currentList.size
fun replaceItems(newUser: List<User>) {
asyncDiffer.submitList(newUser)
}
}
currentList로 현재 아이템을 확인하고, submitList로 리스트 데이터를 변경한다. AsyncListDiffer에서 넘어오는 currentList는 READ ONLY 리스트로 변경이 불가능하기 때문에 currentList의 아이템의 변경은 submitList()를 통해서만 가능하다.
ListAdapter
AsyncListDiffer를 더 쓰기 편하도록 랩핑한 클래스가 바로 ListAdapter이다.
ListAdapter는 사용자를 위해 목록을 추적하고 목록이 업데이트 될 때 어댑터에 알린다.
ListAdapter를 사용한 RecyclerView Adapter는 아래처럼 만들 수 있다.
class UserListAdapter : ListAdapter<User, UserViewHolder>(diffUtil) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = UserViewHolder(
ItemUserBinding.inflate(LayoutInflater.from(parent.context), parent,false)
)
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(getItem(position))
}
fun replaceItems(items: List<User>) {
submitList(items)
}
companion object {
val diffUtil = object: DiffUtil.ItemCallback<User>() {
override fun areContentsTheSame(oldItem: User, newItem: User) =
oldItem == newItem
override fun areItemsTheSame(oldItem: User, newItem: User) =
oldItem.name == newItem.name
}
}
}
// Activity or Fragment ...
adapter.submitList(list)
• getItem(position: Int) : ListAdapter 내부 List Indexing을 할 때 활용된다.
• getCurrentList() : ListAdapter가 가지고 있는 리스트를 가져올 때 사용한다.
• submitList(MutableList<T> list) : 리스트 항목을 변경하고 싶을 때 사용한다.
DiffUtil과 ListAdapter를 활용한 예제
Reference
https://developer.android.com/reference/android/support/v7/util/DiffUtil
https://developer.android.com/reference/androidx/recyclerview/widget/ListAdapter
'Android > Android' 카테고리의 다른 글
[Android] 앱 삭제 후 재설치해도 데이터가 남아있는 문제 (0) | 2022.01.19 |
---|---|
[Android] Databinding error : cannot find symbol (0) | 2021.09.03 |
[Android] Hilt @ViewModelInject is Deprecated (0) | 2021.08.30 |
댓글