[안드로이드] diffUtil을 사용하여 리사이클러뷰 데이터를 자동으로 갱신시키자

0
post-thumbnail

아직도 notifyDataSetChanged() 를 사용하시나요?

기존 리사이클러뷰 어댑터의 문제점

기존에는 데이터의 변경이 이루어질 때마다 notifyDataSetChanged()를 사용하곤 했습니다.
이는 일부 데이터의 변경사항을 처리하기 위해 뷰를 다시 그려 변경사항을 처리하는 메서드입니다.

만약 데이터가 1000개 있고, 하나의 데이터를 삭제하고 notifyDataSetChanged()를 사용하게 된다면 999개의 리스트를 다시 그리는 것이 되는거죠.

물론 한 아이템의 내용을 업데이트 하려면 notifyItemChanged()를 사용하시면 됩니다. 이는 position을 인자로 받아서 해당 위치의 데이터 변경을 알리는 메서드니까요.

그러나 너무 번거롭지 않나요? 데이터를 추가, 삭제를 하려면 notifyDataSetChanged()를 사용하고, 데이터 내용만 변경할 때에는 notifyItemChanged()를 사용하니까요. 게다가 하나의 데이터 삭제에 notifyDataSetChanged()를 사용하기엔 너무 비효율적이라는 생각도 들었습니다.

제가 리팩토링하고 있는 앱도 이런 무분별한 notifyDataSetChanged()를 사용하여 많은 성능적 문제를 야기시켰을거라 생각되는데요, DiffUtil을 적용시켜 성능을 개선시켜봅시다.

DiffUtil

이 문제를 해결하기 위해 안드로이드에서는 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)

AsyncListDifferDiffUtil을 더 단순하게 사용할 수 잇게 해주는 클래스입니다. 자체적으로 멀티 쓰레드에 대한 처리가 되어있기 때문에 개발자가 직접 동기화 처리를 할 필요가 없습니다.

AsyncListDiffer에서 사용하는 대표적인 메서드로는 getCurrentListsubmitList가 있습니다.

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녀석의 짓이 확실합니다.

0개의 댓글