Что делает метод getChangePayload в DiffUtil
Что проверяют
Умеете ли вы обновлять список точечно вместо полного пересчёта адаптера. Вопрос практический: getChangePayload переопределяют те, кто действительно чинил мигающие аватарки и дёргающийся список, а не читал про DiffUtil в общих чертах.
Как отвечать
Метод объявлен в DiffUtil.Callback и в DiffUtil.ItemCallback, вызывается только для пары элементов, у которых areItemsTheSame вернул true, а areContentsTheSame вернул false. Возвращённый объект приезжает в трёхаргументный onBindViewHolder вместе со списком payload. Если список не пуст, обновляют только изменившиеся вьюхи, если пуст, зовут super и делают полный биндинг.
override fun getChangePayload(old: Item, new: Item): Any? {
val changed = mutableSetOf<String>()
if (old.title != new.title) changed += "title"
if (old.isLiked != new.isLiked) changed += "like"
return changed.ifEmpty { null }
}Стоит добавить второй эффект, про который спрашивают реже: непустой payload говорит RecyclerView, что держатель переиспользуется как есть, и стандартная анимация смены содержимого не проигрывается. Именно поэтому пропадает мигание при обновлении счётчика или картинки.
Где спотыкаются
Переопределяют getChangePayload, но не переопределяют вариант onBindViewHolder со списком payload. Метод отрабатывает, значение возвращается, и его никто не читает: элемент перерисовывается целиком, а автор уверен, что оптимизация работает.
Обрабатывают payload и забывают ветку с пустым списком. Тогда при первом показе и после переиспользования держателя элемент остаётся пустым или показывает чужие данные, потому что полный биндинг так и не был вызван.
Считают, что DiffUtil сам всё посчитает в фоне. calculateDiff синхронный, и на длинном списке он подвесит главный поток. Для фонового расчёта есть AsyncListDiffer и ListAdapter, где сравнение уходит в отдельный поток, а результат применяется на главном.