posite 2026. 2. 20. 13:42

DiffUtil

  • 바뀐 부분만 정확히 계산해서 매번 notifyDataSetChanged()를 호출하는 방식보다 효율적이며, 데이터가 꼬일 가능성을 없애줌
  • 백그라운드 처리 및 리스트 관리 직접해야하며 이로 인한 실제 데이터와 소스 간의 싱크가 불일치하는 버그 발생 가능 높음
  • 보일러플레이트 코드 다량 발생

val differ = object : DiffUtil.ItemCallback<Meal>() {
    override fun areItemsTheSame(oldItem: Meal,newItem: Meal) 
        = oldItem.idMeal == newItem.idMeal

    override fun areContentsTheSame(oldItem: Meal,newItem: Meal) = oldItem == newItem
}

ListAdapter

  • DiffUtil 자체적으로 스레드 처리를 해주어 편하게 쓸 수 있음
  • ListAdapter 내부적으로 알아서 백그라운드에서 item들을 비교하고 반영함
  • currentList라는 단일 진실원칙을 강제하기 때문에 안전함
  • submitList() 하나로 리스트 관리가 간편해서 보일러플레이트 코드가 적음

class MealAdapter() : ListAdapter<Meal, MealAdapter.ViewHolder>(differ) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            ItemMealBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    inner class ViewHolder(private val binding: ItemMealBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(data: Meal) {
            //Log.d("MealAdapter", "Binding meal data: $data")
            Glide.with(binding.root.context)
                .load(data.strMealThumb)
                .skipMemoryCache(true)
                .into(binding.mealImg)
            binding.mealName.text = data.strMeal
            binding.mealArea.text = data.strArea
            binding.mealCategory.text = data.strCategory
            binding.mealInstructions.text = data.strInstructions

            binding.mealLink.text = data.strYoutube
            binding.mealInstructions.setOnClickListener {
                Log.d("MealAdapter", "Instructions clicked")
                binding.mealInstructions.toggleExpand()
            }
        }
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
}