아직도 notifyDataSetChanged() 를 사용하시나요?
기존에는 데이터의 변경이 이루어질 때마다 notifyDataSetChanged()
를 사용하곤 했습니다.
이는 일부 데이터의 변경사항을 처리하기 위해 뷰를 다시 그려 변경사항을 처리하는 메서드입니다.
만약 데이터가 1000개 있고, 하나의 데이터를 삭제하고 notifyDataSetChanged()
를 사용하게 된다면 999개의 리스트를 다시 그리는 것이 되는거죠.
물론 한 아이템의 내용을 업데이트 하려면 notifyItemChanged()
를 사용하시면 됩니다. 이는 position
을 인자로 받아서 해당 위치의 데이터 변경을 알리는 메서드니까요.
그러나 너무 번거롭지 않나요? 데이터를 추가, 삭제를 하려면 notifyDataSetChanged()
를 사용하고, 데이터 내용만 변경할 때에는 notifyItemChanged()
를 사용하니까요. 게다가 하나의 데이터 삭제에 notifyDataSetChanged()
를 사용하기엔 너무 비효율적이라는 생각도 들었습니다.
제가 리팩토링하고 있는 앱도 이런 무분별한 notifyDataSetChanged()
를 사용하여 많은 성능적 문제를 야기시켰을거라 생각되는데요, DiffUtil
을 적용시켜 성능을 개선시켜봅시다.
이 문제를 해결하기 위해 안드로이드에서는 DiffUtil
을 제공합니다. 이는 기존의 데이터 리스트와 교체할 데이터 리스트를 비교하여 실질적으로 업데이트가 필요한 아이템을 추려냅니다.
어댑터 클래스에 DiffUtil
을 작성하기 위한 방법으로는 제일먼저 아이템 비교를 위한 콜백 메서드를 정의하는 것입니다.
아래 코드는 콜백 메서드를 정의하는 코드입니다.
private val differCallback = object : DiffUtil.ItemCallback<Diary>() {
override fun areItemsTheSame(oldItem: Diary, newItem: Diary): Boolean {
return oldItem.date == newItem.date
}
override fun areContentsTheSame(oldItem: Diary, newItem: Diary): Boolean {
return oldItem == newItem
}
}
areItemsTheSame
: 기존 아이템과 새로운 아이템이 동일한 지 확인합니다.
areContentsTheSame
: areItemsTheSame
에서 true가 나왔다면 추가적으로 진짜 같은 아이템이 맞는지 확인합니다.
콜백 메서드를 정의해주셨다면 현재 어댑터와 방금 작성한 콜백을 인자로 전달하여 AsyncListDiffer
의 인스턴스를 생성합니다.
val differ = AsyncListDiffer(this, differCallback)
AsyncListDiffer
는 DiffUtil
을 더 단순하게 사용할 수 잇게 해주는 클래스입니다. 자체적으로 멀티 쓰레드에 대한 처리가 되어있기 때문에 개발자가 직접 동기화 처리를 할 필요가 없습니다.
AsyncListDiffer
에서 사용하는 대표적인 메서드로는 getCurrentList와 submitList가 있습니다.
getCurrentList
: 현재 어댑터에서 사용하는 아이템 리스트에 접근하려고 할때 사용합니다.
submitList(List<T> value)
: 리스트를 인자로 받아서 인자로 받은 리스트로 아이템 리스트를 갱신하고자 할때 사용합니다.
이 메서드들은 액티비티나 프래그먼트에서 다음과 같이 사용됩니다. 다음 코드는 리사이클러뷰를 스와이프로 삭제하기 위한 onSwiped 콜백 메서드를 정의하는 코드입니다.
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition
val diary = diaryAdapter.differ.currentList[position]
viewModel.deleteDiary(diary)
Snackbar.make(requireView(), R.string.deleteData, Snackbar.LENGTH_LONG)
.apply {
viewModel.deleteDiary(diary)
setAction(R.string.undo) {
viewModel.saveDiary(diary)
}
show()
}
}
이런식으로 currentList를 사용하여 어댑터가 유지하고 있는 리스트에 접근할 수 있습니다.
다음은 submitList의 활용사례입니다. 아래 코드는 뷰모델과 LiveData를 이용하여 데이터가 변경될 때마다 최신 데이터를 리사이클러뷰에 갱신하는 코드입니다.
viewModel.filteredList.observe(viewLifecycleOwner) {
diaryAdapter.differ.submitList(it)
}
애니메이션을 지정하지 않았는데 애니메이션 처리가 되었네요. DiffUtil녀석의 짓이 확실합니다.