[Android] RecyclerView Adapter 와 ListAdapter

jeunguri·2023년 2월 4일
0

android

목록 보기
12/13

RecyclerView Adapter

  • View 객체를 재사용하기 위한 ViewHolder 객체 생성
  • 해당 객체에 데이터 리스트를 주입
  • 데이터 리스트의 변경을 UI에 반영 -> notifyDataSetChanged()를 사용
fun setItem(newItem: List<Item>) {
	items.clear()
    items.addAll(newItem)
    notifyDataSetChanged()

그런데 데이터가 변경되는 방식을 확인하고 그때마다 notify를 일일히 해주는 것은 매우 번거롭고, 갱신이 필요없는 ViewHolder를 같이 갱신하는 불필요한 작업이 생길수도 있다.
모든 리스트를 업데이트 하는 방식 말고, 리스트의 차이를 계산하는 DiffUtil을 사용하면 이를 해결할 수 있다.



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
    두 아이템이 동일한 데이터를 가지는지 확인



ListAdapter

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
    데이터를 갱신



RecyclerView.ListAdapter 예제

  • viewBinding 사용
  • retrofit2 이용해서 통신
  • Glide 사용

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) {
                        // 실패 처리에 대한 구현
                    }
                })
        }
    }
}

0개의 댓글