일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- 플레이스토어
- XML
- NavHost
- sharedFlow
- 커스텀뷰
- 안드로이드
- 파이어베이스
- 뷰
- Flow
- Android
- 클린아키텍처
- ListAdapter
- UiState
- MVVM
- 리사이클러뷰
- Authentication
- 코틀린
- Build variants
- cleanarchitecture
- Jetpack
- Kotlin
- 컴포즈
- 알고리즘
- NavController
- 회원가입
- coroutine
- 로그인
- Compose
- 코딩테스트
- DiffUtil
- Today
- Total
Grusie 안드로이드 개발 기술 블로그
[Android] 리사이클러뷰 DiffUtil 사용법 및 단일 체크 처리하기 본문
리사이클러뷰 단일 체크에 대해 고민해보았다. onBindViewHolder에서 처리를 하였더니, 깜빡임이 발생해 찾았던 방식이 payload였고, 회사 프로젝트에도 적용을 한 적이 있었다. DiffUtil은 리사이클러뷰의 아이템 추가/수정/삭제 등을 진행하여 변경사항을 알릴 때 기존 notifyDataSetChanged 를 사용했을 때에 비해, 성능면에서 좋다는 것만 생각하고 있었으니 조금 더 공부를 해보게 되었다.
기존 노션 페이지에서도 한 번 정리해둔 적이 있으니 참고하면 좋을 것이다.
https://grusie.notion.site/6d922ec6b7b746d0aa15a0b9a4a953a5?pvs=4
안드로이드 공부 | Notion
이론 공부
grusie.notion.site
DiffUtil이란?
리사이클러뷰에서 데이터를 수정하였을 때, notifyDataSetChanged를 사용하게 될 경우 변경되지 않은 부분까지 새로 그리게 되어 앱 성능에 악영향을 미치게 된다.
물론 변경되어야 할 위치를 정확히 알고 있으면, 그 부분만 변경을 하게 할 수도 있다.
하지만 데이터가 삭제가 된다거나, 여러 개가 수정이 되어 정확한 위치를 알 수 없다고 했을 경우 그냥 RecyclerView에서는 notifyDataSetChanged를 사용하게 되었을 것이다.
DiffUtil은 이전 데이터와 현재 데이터 목록의 차이를 계산하여 업데이트 해야할 데이터만 갱신을 할 수 있도록 도와주는 클래스이다.
사용법
DiffUtil.Callback 이라는 추상 클래스를 사용하며, 이에 해당하는 메소드를 오버라이딩 하여 사용한다.
getOldListSize() : 이전 목록의 크기를 반환
getNewListSize() : 새로운 목록의 크기를 반환
areItemsTheSame() : 두 항목이 같은 객체인지 반환
areContentsTheSame() : 두 항목의 데이터가 같은지 반환 (areItemsTheSame()이 true일 때만 호출)
getChangePayload() : areItemsTheSame()이 true이면서 areContentsTheSame()이 true일 경우, 변경 내용에 대한 페이로드를 가져온다.
object ColorCardDiffUtilCallback: DiffUtil.ItemCallback<ColorCardInfo>() {
override fun areItemsTheSame(
oldItem: ColorCardInfo,
newItem: ColorCardInfo,
): Boolean {
return oldItem.id == newItem.id
//고유 아이디 값을 비교하는 것이 좋다.
}
override fun areContentsTheSame(
oldItem: ColorCardInfo,
newItem: ColorCardInfo,
): Boolean {
Log.d("confirm colorCardAdapter ", "${oldItem.isSelected == newItem.isSelected}, $oldItem, $newItem")
return oldItem == newItem
//객체가 같은지 비교
}
}
이렇게 DiffUtil을 구현해두고, 리사이클러뷰에서 상속받아 사용하는 방법이 있고, 백그라운드에서 수행할 수 있도록 해주는 클래스인 AsyncListDiffer를 생성해서 사용할 수도 있고, ListAdapter를 사용하는 방법이 있다.
ListAdapter
우리는 AsyncListDiffer를 더 편리하게 사용하기 위한 ListAdapter 사용을 한다.
class ColorCardAdapter(private val colorClickListener: (Int) -> Unit):
ListAdapter<ColorCardInfo, RecyclerView.ViewHolder>(ColorCardDiffUtilCallback) {
DiffUtil.callback을 구현한 ColorCardDiffUtilCallback을 매개변수로 같는 ListAdapter를 상속하여 어댑터를 생성한다.
ListAdapter는 submitList() 함수를 사용해 데이터를 변경 할 수 있다.
이 정도 배경지식을 가지고, 단일 체크를 구현해보자.
ViewHolder
fun bind(colorCardInfo: ColorCardInfo) {
binding.run {
isSelected = colorCardInfo.isSelected == true
}
itemView.setOnClickListener {
colorClickListener(colorCardInfo.position!!)
}
}
fun update(bundle: Bundle) {
if(bundle.containsKey(ColorCardAdapter.CHECK_PAYLOAD)) {
val isSelected = bundle.getBoolean(ColorCardAdapter.CHECK_PAYLOAD)
binding.isSelected = isSelected
}
}
우선 처음 세팅을 하고 클릭 리스너를 달아주는 bind 함수와, 상태가 변경되어 페이로드 값을 받아 변경된 값을 처리해줄 update함수를 만들고
DiffUtil
override fun getChangePayload(oldItem: ColorCardInfo, newItem: ColorCardInfo): Any? {
if(oldItem.id == newItem.id) {
return if(oldItem.isSelected == newItem.isSelected) {
super.getChangePayload(oldItem, newItem)
} else {
val diff = Bundle()
diff.putBoolean(ColorCardAdapter.CHECK_PAYLOAD, newItem.isSelected!!)
diff
}
}
return super.getChangePayload(oldItem, newItem)
}
변경이 되었을 때, payload값을 보낼 getChangePaload를 오버라이딩 해 수정해준다.
필자는 현재 아이템의 isSelected 값이 변경되었으면 보내는 형태로 처리하였다.
Activity
private val colorCardAdapter: ColorCardAdapter by lazy {
ColorCardAdapter { position ->
val newList = colorCardAdapter.currentList.toMutableList()
val prevSelectedPosition = colorCardAdapter.selectedItemPosition
if (prevSelectedPosition != position) {
//이전 값 처리
val prevSelectedItem = newList[prevSelectedPosition].copy(isSelected = false)
newList[prevSelectedPosition] = prevSelectedItem
//현재 값 처리
val selectedItem = newList[position].copy(isSelected = true)
newList[position] = selectedItem
colorCardAdapter.selectedItemPosition = position
colorCardAdapter.submitList(newList)
}
}
}
어댑터를 생성하면서 동시에, 클릭 이벤트에 대한 람다를 구현해주었으며, adapter 내에 있는 기존에 클릭된 포지션과 현재 클릭된 포지션을 비교하여, 다를 경우, 이전 값과 현재 값을 변경 해주고, submitList()를 호출한다.
원하는대로 한가지 데이터만 체크 되는 것을 볼 수 있다. 필자는 저 중 하나가 꼭 체크가 되어있어야 하기에, 클릭 되었으며, 이전과 다른 곳을 클릭했으면, 무조건 true로 되도록 처리 해두었다.
만약 클릭 했을 때 아이템이 깜빡인다면, DiffUtil 부분을 다시 보고, 수정해보자, areItemsTheSame()을 객체간의 ==로 처리하는 것이 아니라, 유효한 id 값이여야 한다.
참고
https://rocketnoning.tistory.com/46
RecyclerView DiffUtil 활용 및 성능 끌어올리기
Recycler View 데이터를 리스트 형태로 화면에 표시하는 컨테이너 역할을 수행합니다. 어뎁터 리사이클러뷰에 표시될 아이템 뷰를 생성하는 역할은 어댑터가 담당합니다. 사용자 데이터 리스트로
rocketnoning.tistory.com
https://thdev.tech/kotlin/2020/09/22/kotlin_effective_03/
data class를 활용하여 RecyclerView.DiffUtil을 잘 활용하는 방법 |
I’m an Android Developer.
thdev.tech
https://ppeper.github.io/android/android-diffutil/#listadapter
안드로이드 RecyclerView의 DiffUtil 알아보기
DiffUtil 넌 뭐니 안드로이드를 공부하거나 개발하다보면 대부분 리스트를 보여주기 위하여 RecyclerView 의 사용을 하게되고, 리스트의 데이터가 변하게 되면 notifyDataSetChange() 를 호출하여 리사이클
ppeper.github.io
후기
DiffUtil에 대해 조금 더 알게 되었던 것 같다. 이전에는 notifyItemChanged에 payloads를 보냈었는데, DiffUtil에서도 가능하다는 것을 알게 되었고, 리사이클러뷰의 속도 면에서도 개선이되며, ListAdapter를 사용하여 submitList로 기존 리스트를 대체 하는 것이기에 뷰를 재사용 하면서 오류가 발생하는 일이 없어진 것 같아서 좋았다.
물론 현재는 아이템의 값을 뷰에서 가지고 있었으나, 뷰모델에서 관리하도록 수정하여야 한다.
'안드로이드 개발' 카테고리의 다른 글
[Android] release 버전에서 Gson 파싱이 안 되던 오류(코드 난독화 문제) (0) | 2024.05.07 |
---|---|
[Android] 플레이 스토어 버전 업데이트 관리하기 (1) | 2024.05.07 |
[Android] CallAdapter로 레트로핏 에러 처리하기 (1) | 2024.04.25 |
[Android] 버전 카탈로그로 Gradle 의존성 관리하기 (0) | 2024.04.24 |
[Android] 테스트 코드 작성하기(단위테스트 - unit test) (1) | 2024.04.03 |