ListAdapter란?
ListAdapter는 RecyclerView.Adapter의 확장된 형태로, RecyclerView의 데이터를 리스트 형태로 관리하며, 효율적인 데이터 리스트 업데이트를 위해 DiffUtil을 내부적으로 사용한다. 기존의 RecyclerViewAdapter와 ListAdapter의 주요 차이점은 데이터의 변경을 관리하는 방식에 있다.
조금 더 보태자면 기존의 RecyclerViewAdapter는 정적인, 즉 고정된 데이터를 보여주기에 최적화되어 있고 ListAdapter는 데이터가 동적으로 추가되고 삭제되는 일들이 많을 때 효율적으로 동작하기 위해 최적화되어있다고 생각하면 된다.
RecyclerViewAdapter와의 차이점
RecyclerViewAdapter
- 데이터 변경을 감지하고 UI를 업데이트하기 위해서는 개발자가 명시적으로 notifyDataSetChanged(), notifyItemInserted(), notifyItemRemoved() 등의 메서드를 호출해야 한다.
- 데이터가 변경될 때마다 전체 리스트를 업데이트하거나, 특정 위치의 데이터가 변경되었다는 것을 직접 알려줘야 한다.
- 성능상의 이유로, 대량의 데이터가 변경될 때 비효율적일 수 있다. ( 모든 아이템을 전부 다시 그리기 때문)
ListAdapter
- 내부적 으로 AsyncListDiffer를 사용하여 데이터의 변경사항을 비동기적으로 계산하고, 필요한 최소한의 업데이트를 RecyclerView에 알려준다. (DiffUtil 메소드를 통해서 변경된 부분만 뷰를 그림)
- 개발자는 단순히 새로운 리스트를 submitList() 메서드를 통해 전달하면, ListAdapter가 나머지를 처리합니다.
- 데이터의 변경 사항이 자동으로 계산되어 효율적인 업데이트가 가능합니다.
DiffUtil이란?
DiffUtil은 두 리스트 간의 차이점을 계산하는 유틸리티 클래스로, 어떤 항목이 추가, 제거, 변경되었는지를 찾아낸다. 이를 위해 DiffUtil.Callback을 상속받은 클래스를 만들어서 두 리스트의 항목이 같은지(areItemsTheSame()), 내용이 같은지(areContentsTheSame())를 비교하는 로직을 구현해야 한다.
DiffUtil은 이러한 정보를 바탕으로 최소한의 업데이트로 RecyclerView를 갱신할 수 있는 'DiffResult'를 생성합니다. 이 결과는 RecyclerView의 어댑터에 적용되어, 효율적으로 UI를 업데이트할 수 있게 도와준다.
ListAdapter를 사용하면 이 모든 과정을 더 쉽게 처리할 수 있게 되어, 데이터가 변경될 때마다 RecyclerView를 더 효율적으로 업데이트할 수 있습니다.
ListAdapter 사용법
class ContactAdapter(var viewType: Int) :
ListAdapter<ContactData, RecyclerView.ViewHolder>(diffUtil) {
companion object {
private val diffUtil = object : DiffUtil.ItemCallback<ContactData>() {
override fun areItemsTheSame(oldItem: ContactData, newItem: ContactData): Boolean {
// 연락처가 같은지 확인
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: ContactData, newItem: ContactData): Boolean {
// 모든 필드가 같은지 확인 (data class의 equals 사용)
return oldItem == newItem
}
}
const val TYPE_LIST = 0
const val TYPE_GRID = 1
}
override fun getItemViewType(position: Int): Int {
return viewType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// ViewHolder 생성 로직
return when (viewType) {
TYPE_LIST -> ListViewHolder(
ItemRecyclerViewListBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
)
)
TYPE_GRID -> GridViewHolder(
ItemRecyclerViewGridBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
)
)
else -> throw IllegalArgumentException("Invalid view type")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// ViewHolder 바인딩 로직
when (holder.itemViewType) {
TYPE_LIST -> (holder as ListViewHolder).bind(getItem(position))
TYPE_GRID -> (holder as GridViewHolder).bind(getItem(position))
}
}
// ViewHolder 구현
inner class ListViewHolder(private val binding: ItemRecyclerViewListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(contact: ContactData) {
/* 아이템 바인딩 로직 */
}
}
inner class GridViewHolder(private val binding: ItemRecyclerViewGridBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(contact: ContactData) {
/* 아이템 바인딩 로직 */
}
}
위 코드는 연락처 앱의 어댑터를 구성한 코드이다. 2개의 뷰 타입을 볼 수 있게 하기 위해 ListViewHolder와 GridViewHolder를 생성한 점을 제외하면 일반 리스트어댑터의 구성과 동일하다.
ListAdapter를 상속받아서 그 안에 데이터와 뷰 홀더를 넣어준다. 기존에 RecyclerViewAdapter에서는 인자로 dataList를 받아왔지만 ListAdapter는 변경이 있을 때 submitList를 통해 리스트를 받아오기에 인자가 필요없다.
내부적으로 getItem 메소드를 통해서 ListAdapter의 currentList를 받아와 RecyclerViewAdapter의 mItems[position].Item 을 대체한다.
어댑터 구현이 완료되면 어댑터를 세팅하고 어댑터에 데이터를 넣을 때는아래와 같다.
object ContactManager {
val contactList: MutableList<ContactData> = mutableListOf()
fun getList(): List<ContactData> {
return contactList.toList()
}
}
contactAdapter.submitList(ContactManager.getList())
중요한 점은 기존의 contactList는 MutableList이지만 호출할 때는 toList()로 변환하여 List형태로 submit한다는 점이다.
ListAdapter의 DiffUtil 이 받아오는 리스트의 주소값이 같으면 동작을 안하기 때문에 toList로 변환하여서 주소값을 변경시켜주어서 다른 리스트라는 것을 알려주어야 한다.
'Kotlin' 카테고리의 다른 글
Android 구글 지도 앱 (GoogleMap App) 만들기 -1편 (0) | 2024.01.24 |
---|---|
SharedPreferences를 이용한 데이터 저장 (1) | 2024.01.23 |
Kotlin 비동기 프로그래밍 (2) | 2023.11.28 |
Kotlin Scope Function (let, with, apply, run, also) (0) | 2023.11.28 |
Kotlin 문자열 숫자 변환 기능 + 2개 이상의 값 리턴 (0) | 2023.11.27 |