일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 안드로이드
- 막내의 막무가내
- 프로그래머스 알고리즘
- 프래그먼트
- 막내의막무가내
- 2022년 6월 일상
- 막내의막무가내 목표 및 회고
- 안드로이드 sunflower
- 막내의막무가내 프로그래밍
- 막내의막무가내 안드로이드 코틀린
- 주엽역 생활맥주
- 막내의막무가내 안드로이드 에러 해결
- 막무가내
- 막내의막무가내 플러터
- 막내의막무가내 안드로이드
- 막내의막무가내 플러터 flutter
- 부스트코스
- flutter network call
- 주택가 잠실새내
- Fragment
- 안드로이드 Sunflower 스터디
- 막내의막무가내 알고리즘
- 막내의막무가내 일상
- 막내의막무가내 rxjava
- 막내의막무가내 SQL
- 막내의 막무가내 알고리즘
- 막내의막무가내 코틀린
- 막내의막무가내 코볼 COBOL
- 막내의막무가내 코틀린 안드로이드
- 부스트코스에이스
- Today
- Total
막내의 막무가내 프로그래밍 & 일상
[안드로이드] RecyclerView Diffutil, ListAdapter예제 정리 본문
[2021-04-27 업데이트]
[개념(출처) 참고 및 공부자료들]
thdev.tech/kotlin/2020/09/22/kotlin_effective_03/
velog.io/@l2hyunwoo/Android-RecyclerView-DiffUtil-ListAdapter
developer.android.com/codelabs/kotlin-android-training-diffutil-databinding#0
developer.android.com/reference/androidx/recyclerview/widget/ListAdapter
기본내용과 원리는 위 참고 사이트들에 너무나도 잘 나와있어 저는 간략히 정리만 조금 하겠습니다. 제가 쓰는 것보다 저분들의 포스팅을 한번더 보는게 이득입니다. ㅋㅋㅋ
[DiffUtil 이란?]
요약하면 안드로이드 어댑터에서 현재 데이터 리스트와 교체될 데이터 리스트를 비교하여 무엇이 다른지(바꼈는지) 알아내는 클래스로 리사이클러뷰에서 기존 아이템리스트에 수정 혹은 변경이 있을 시 전체를 갈아치우는게 아니라 변경되야하는 데이터만 빠르게 바꿔주는 역할을 합니다.
[함수 정리]
- getOldListSize : 현재 리스트에 노출하고 있는 List size
- getNewListSize : 새로 추가하거나, 갱신해야 할 List size
- areItemsTheSame : 현재 리스트에 노출하고 있는 아이템과 새로운 아이템이 서로 같은지 비교한다. 보통 고유한 ID 값을 체크한다.
- areContentsTheSame : 현재 리스트에 노출하고 있는 아이템과 새로운 아이템의 equals를 비교한다.
[ListAdapter 란?]
ListAdapter는 안드로이드 문서 그대로 번역하면 RecyclerView.Adapter 를 베이스로 한 클래스로 RecyclerView 의
List 데이터를 표현해주며 List를 백그라운드 스레드에서 diff(차이)를 처리하는 특징이 있습니다.
이 클래스는 AsyncListDiffer 아이템 접근 및 카운팅에 대한 어댑터 공통 기본 동작을 구현 하는 편리한 Wrapper 입니다.
LiveData <List>를 사용하면 어댑터에 데이터를 쉽게 제공 할 수 있지만 필수는 아닙니다 submitList(List). 새 목록을 사용할 수있을 때 사용할 수 있습니다.
그래도 ListAdapter의 기본적인 메소드만 몇개 살펴보겠습니다.
getItem(position: Int) : protected method로 클래스 내부에서 사용하며 어댑터 내 아이템 List 를 인덱싱할때 사용합니다. 기존 val items : List<User>식으로 어댑터내에서 선언해서 items[position] 식으로 사용했는데 ListAdapter + Diffutil에서는 아이템 리스트도 알아서 관리해주기 때문에 기본적으로 리스트 선언이 필요없고 이 리스트의 아이템을 가져오는데 사용(대체)됩니다.
getCurrentList() : 어댑터가 가지고 있는 리스트를 가져올 때 사용합니다.
submitList(List<T> list): 리스트 항목을 변경하고 싶을 때 사용합니다 기존 일반 어댑터의 add(), notifyDataSetChanged()를 대체한다고 보면 됩니다.
제가 짠 예제 코드입니다.
[ListAdapter]
데이터바인딩 적용
package com.mtjin.cnunoticeapp.views.employ
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.mtjin.cnunoticeapp.R
import com.mtjin.cnunoticeapp.data.employ.EmployNotice
import com.mtjin.cnunoticeapp.databinding.ItemEmployBinding
class EmployAdapter(
private val itemClick: (EmployNotice) -> Unit,
private val numClick: (EmployNotice) -> Unit
) : ListAdapter<EmployNotice, EmployAdapter.ViewHolder>(
diffUtil
) {
//리스트 선언 필요X
//private val items = mutableListOf<EmployNotice>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ItemEmployBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_employ,
parent,
false
)
val viewHolder = ViewHolder(binding)
binding.apply {
root.setOnClickListener {
itemClick(getItem(viewHolder.adapterPosition)) //getItem()으로 아이템 가져옴
}
employTvNum.setOnClickListener {
numClick(getItem(viewHolder.adapterPosition))
}
}
return viewHolder
}
//getItemCount() 오버라이딩 메서드 사라짐
//override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position)) //변경된 점 -> getItem(position) 메서드가 생겼다.
}
class ViewHolder(private val binding: ItemEmployBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: EmployNotice) {
binding.item = item
binding.executePendingBindings()
}
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<EmployNotice>() {
override fun areContentsTheSame(oldItem: EmployNotice, newItem: EmployNotice) =
oldItem == newItem
override fun areItemsTheSame(oldItem: EmployNotice, newItem: EmployNotice) =
oldItem.link == newItem.link
}
}
}
[ViewModel]
그냥 가져오는거와 페이징만큼 가져오는 함수 2개가 존재합니다. 가져온 데이터는 라이브데이터 리스트에 세팅되고 이 리스트는 xml에 데이터바인딩 되어있습니다.
package com.mtjin.cnunoticeapp.views.employ
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.mtjin.cnunoticeapp.base.BaseViewModel
import com.mtjin.cnunoticeapp.data.employ.EmployNotice
import com.mtjin.cnunoticeapp.data.employ.source.EmployNoticeRepository
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
class EmployNoticeViewModel(private val repository: EmployNoticeRepository) :
BaseViewModel() {
private val _noticeList = MutableLiveData<MutableList<EmployNotice>>()
val noticeList: LiveData<MutableList<EmployNotice>> get() = _noticeList
fun requestNotice() {
compositeDisposable.add(
repository.requestNotice()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { showProgress() }
.doAfterTerminate { hideProgress() }
.subscribe({
_noticeList.value = it as MutableList<EmployNotice>?
}, {
Log.d(TAG, "" + it)
})
)
}
fun requestMoreNotice(offset: Int) {
compositeDisposable.add(
repository.requestMoreNotice(offset)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { showProgress() }
.doAfterTerminate { hideProgress() }
.subscribe({ notices ->
val pagingNoticeList = _noticeList.value
pagingNoticeList?.addAll(notices)
_noticeList.value = pagingNoticeList
}, {
Log.d(TAG, "" + it)
})
)
}
companion object {
const val TAG = "EmployNoticeViewModel"
}
}
[바인딩어댑터]
다른 예제코드와 달리 액티비티에서 Observer 콜백으로 안받고 데이터바인딩 어댑터로 세팅했습니다.
대부분의 예제가 ViewModel LiveData를 액티비티에서 콜백받아 submitList()하는데 저는 다르게 했는데 밑과 같은 이유가 발생하는데 해결은 하긴했지만 아직 정확한 원인을 못찾고있습니다.
(만약 보시고 원인을 아시면 답변해주시면 감사하겠습니다. ㅠㅠ)
밑 데이터바인딩어댑터 적은 주석 이슈는 정확한 원인은 잘 모르지만
@BindingAdapter("setEmployItems")
fun RecyclerView.setEmployAdapterItems(items: List<EmployNotice>?) {
items?.let {
(adapter as EmployAdapter).submitList(it.toMutableList())
//그냥 it으로 넣으면 첫 데이터만 불러와지고 이후 무한스크롤 데이터들이 갱신이 안된다.
//페이징 데이터는 여기까지 잘넘어오는데 갱신은 안되는 이유 알아봐야할 것 같음
}
}
밑과 같은 이유같기 때문인거같기도..
무한스크롤 관련 코드
@BindingAdapter("employEndlessScroll")
fun RecyclerView.setEmployEndlessScroll(
viewModel: EmployNoticeViewModel
) {
val scrollListener =
object : EndlessRecyclerViewScrollListener(layoutManager as LinearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?) {
viewModel.requestMoreNotice(totalItemsCount + 1)
}
}
this.addOnScrollListener(scrollListener)
}
댓글과 공감은 큰 힘이 됩니다. !!
'안드로이드 > 코틀린 & 아키텍처 & Recent' 카테고리의 다른 글
[안드로이드] 리사이클러뷰(RecyclerView) 어댑터 베이스 정리 코드 (복붙용) (0) | 2021.01.16 |
---|---|
[안드로이드] 충남대 컴공 앱 (6) | 2021.01.11 |
[안드로이드] 데이터바인딩 어댑터 (DatabindingAdapter) 및 확장함수 모음 정리 (0) | 2021.01.07 |
[안드로이드] 안드로이드 11 버전 권한(Permission), 위치(Location) 관련 변경사항 총정리!!! (0) | 2021.01.07 |
[안드로이드] Room 로컬 데이터베이스에서 기본형이 아닌 객체 필드값 저장방법 (feat. @Embeded, @TypeConverter) (6) | 2020.12.17 |