notifyDataSetChanged()
를 사용fun setItem(newItem: List<Item>) {
items.clear()
items.addAll(newItem)
notifyDataSetChanged()
그런데 데이터가 변경되는 방식을 확인하고 그때마다 notify를 일일히 해주는 것은 매우 번거롭고, 갱신이 필요없는 ViewHolder를 같이 갱신하는 불필요한 작업이 생길수도 있다.
모든 리스트를 업데이트 하는 방식 말고, 리스트의 차이를 계산하는 DiffUtil을 사용하면 이를 해결할 수 있다.
DifUtil
은 기존 데이터 리스트와 업데이트된 데이터 리스트를 비교해서 차이를 알아내는 유틸리티 클래스이다.
두 데이터 리스트를 비교한 뒤 변한 부분만 파악하여 RecyclerView에 반영할 수 있다.
DifUtil을 더 단순하게 사용하기 위해 AsyncListDiffer
클래스를 사용할 수 있다. 자체적으로 멀티 스레드에 대한 처리가 되어 있기 때문에 개발자가 직접 동기화 처리를 할 필요가 없어진다.
companion object {
val diffUtil = object : DiffUtil.ItemCallback<Book>() {
override fun areContentsTheSame(oldItem: Book, newItem: Book) =
oldItem == newItem
override fun areItemsTheSame(oldItem: Book, newItem: Book) =
oldItem.id == newItem.id
}
}
areItemsTheSame
비교 대상인 두 객체가 동일 객체인지 확인
areContentsTheSame
두 아이템이 동일한 데이터를 가지는지 확인
RecyclerView.Adapter를 베이스로 한 클래스로 AsyncListDiffer를 더욱 간편하게 사용할 수 있도록 해준다.
// ListAdapter<데이터 클래스, 뷰홀더>(콜백)
class BookAdapter() : ListAdapter<Book, BookAdapter.ViewHolder>(diffUtil) {
inner class ViewHolder(private val binding: ItemBookBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(bookModel: Book) {
binding.titleTextView.text = bookModel.title
binding.descriptionTextView.text = bookModel.description
Glide
.with(binding.coverImageView.context)
.load(bookModel.coverSmallUrl)
.into(binding.coverImageView)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ItemBookBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(currentList[position])
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<Book>() {
override fun areContentsTheSame(oldItem: Book, newItem: Book) =
oldItem == newItem
override fun areItemsTheSame(oldItem: Book, newItem: Book) =
oldItem.id == newItem.id
}
}
}
currentList
현재 데이터를 불러옴
submitList
데이터를 갱신
Model
data class HouseModel(
val id: Long,
val title: String,
val price: String,
val imgUrl: String
)
Dto
data class HouseDto(
val items: List<HouseModel>
)
Service
interface HouseService {
@GET("v3/8d0d82f4-54fe-4858-9c03-f68df22e309c")
fun getHouseList(): retrofit2.Call<HouseDto>
}
Adapter
class HouseAdapter : ListAdapter<HouseModel, HouseAdapter.ViewHolder>(diffUtil) {
inner class ViewHolder(var binding: HouseItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: HouseModel) = with(binding) {
titleTextView.text = item.title
priceTextView.text = item.price
Glide
.with(thumbnailImageView.context)
.load(item.imgUrl)
.transform(CenterCrop(), RoundedCorners(dpToPx(thumbnailImageView.context, 12)))
.into(thumbnailImageView)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(HouseItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(currentList[position])
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<HouseModel>() {
override fun areItemsTheSame(oldItem: HouseModel, newItem: HouseModel): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: HouseModel, newItem: HouseModel): Boolean {
return oldItem == newItem
}
}
}
private fun dpToPx(context: Context, dp: Int): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), context.resources.displayMetrics).toInt()
}
}
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val adapter = HouseAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.recyclerView.adapter = adapter
getHouseListFromAPI()
}
private fun getHouseListFromAPI() {
val retrofit = Retrofit.Builder()
.baseUrl("https://run.mocky.io/")
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(HouseService::class.java).also {
it.getHouseList()
.enqueue(object : Callback<HouseDto> {
override fun onResponse(call: Call<HouseDto>, response: Response<HouseDto>) {
if (response.isSuccessful.not()) {
Log.d("MainActivity", "response fail!!")
return
}
response.body()?.let { dto ->
Log.d("MainActivity", dto.toString())
adapter.submitList(dto.items)
}
}
override fun onFailure(call: Call<HouseDto>, t: Throwable) {
// 실패 처리에 대한 구현
}
})
}
}
}