Почему в DiffUtil два Callback’а, а не один

Ответ

DiffUtil использует два Callback'а (DiffUtil.Callback и DiffUtil.ItemCallback) для разделения логики сравнения элементов и их идентификации.

DiffUtil.Callback (для RecyclerView.Adapter):

  • Сравнивает старый и новый списки целиком
  • Используется в ListAdapter/AsyncListDiffer
  • Требует реализации getOldListSize(), getNewListSize(), areItemsTheSame(), areContentsTheSame()

ItemCallback (для AsyncListDiffer):

  • Оптимизирован для пошагового сравнения
  • Упрощает работу с Paging и асинхронными обновлениями

Пример ItemCallback:

val diffCallback = object : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(old: User, new: User) = old.id == new.id
    override fun areContentsTheSame(old: User, new: User) = old == new
}

Ответ 18+ 🔞

Эй, слушай, тут про эту вашу DiffUtil штуку. Ну, понимаешь, чтобы в RecyclerView всё красиво обновлялось, а не просто notifyDataSetChanged(), от которого всё дергается, как будто в телевизоре помехи.

Так вот, у этой штуки есть два главных коллбэка, как два брата-близнеца, но один — бухгалтер, а второй — каскадёр. DiffUtil.Callback — это тот самый бухгалтер, дотошный такой. Он садится и сравнивает два списка — старый и новый — от начала до конца, каждый элемент. Ему надо всё знать: сколько было, сколько стало (getOldListSize, getNewListSize), это один и тот же предмет или нет (areItemsTheSame), и если да, то не поменялось ли в нём чего (areContentsTheSame). Честно, иногда от этой бюрократии терпения ноль, ебать. Но без него никуда, особенно в обычном RecyclerView.Adapter.

А вот ItemCallback — это уже каскадёр, оптимизированный под движуху. Он не смотрит на весь список сразу, а может сравнивать элементы по одному, что для асинхронных штук, типа того же Paging библиотеки — просто песня. Он проще и используется внутри AsyncListDiffer или ListAdapter. По сути, это тот же самый принцип, но упакованный по-современному.

Вот смотри, как это выглядит на практике, чтобы не быть голословным. Берёшь и пишешь:

val diffCallback = object : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(old: User, new: User) = old.id == new.id
    override fun areContentsTheSame(old: User, new: User) = old == new
}

Всё, бля! В первой строчке говоришь: «Чувак, если айдишники совпали — это один и тот же юзер, даже если он теперь блондин». Во второй: «А если и все остальные поля одинаковые, то вообще овердохуища, и перерисовывать нечего». Красота!

Главное — не перепутать, где какого брата использовать, а то будет тебе хиросима в списке вместо плавных анимаций.