getChangePayload in DiffUtil

getChangePayload in DiffUtil

DiffUtilのgetChangePayloadを利用して、リストのアイテムの更新をより細かく制御しようというお話です。

▼サンプルコードはこちら
https://github.com/rmakiyama/recyclerview-playground

E6d26a51159a7863cac28e9d12ccd389?s=128

rmakiyama

July 10, 2020
Tweet

Transcript

  1. getChangePayload in DiffUtil potatotips #70 2020/07/10 rmakiyama

  2. ɹɹࣗݾ঺հ •຀ࢁྎ •Radiotalkגࣜձࣾ •AndroidΤϯδχΞ • @_rmakiyama • rmakiyama ref. https:"//github.com/rmakiyama/recyclerview-playground

  3. Ϧετͷࠩ෼ߋ৽

  4. Androidʹ͓͚ΔϦετUIͷදࣔ • RecyclerViewΛϨΠΞ΢τʹ௥Ճ • LayoutManagerΛઃఆ͠഑ஔํ๏Λࢦఆ • RecyclerView.AdapterΛηοτ

  5. RecyclerViewͷߋ৽

  6. RecyclerView.AdapterͰͷϦετߋ৽ • notifyDataSetChanged • notifyItemChanged • notifyItemInserted • notifyItemMoved •

    bla bla bla…
  7. DiffUtil • ̎ͭͷϦετͷࠩ෼Λޮ཰Α͘ܭࢉ • ܭࢉ݁Ռ͔ΒRecyclerViewΛ͍͍ײ͡ʹߋ৽ • ద੾ͳnotifyϝιουΛݺΜͰ͘ΕΔ • ద੾ͳΞχϝʔγϣϯ͕ݺ͹ΕΔ •

    ίʔϧόοΫΛ࣮૷ͯ͠࢖͏
  8. DiffUtil.Callback public abstract static class Callback { public abstract int

    getOldListSize(); public abstract int getNewListSize(); public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition); public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition); public Object getChangePayload(int oldItemPosition, int newItemPosition) { return null; } }
  9. DiffUtil.ItemCallback public abstract static class ItemCallback<T> { public abstract boolean

    areItemsTheSame(@NonNull T oldItem, @NonNull T newItem); public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem); public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) { return null; } }
  10. DiffUtil.ItemCallback • areItemsTheSame • ΞΠςϜ͕ಉ͔͡൑ఆ݁͠ՌΛฦ͢ • areContentsTheSame • ΞΠςϜͷத਎͕ಉ͔͡൑ఆ݁͠ՌΛฦ͢

  11. areItemsTheSame • ΞΠςϜ͕ಉ͔͡൑ఆ • ҰҙͳࣝผࢠͰൺֱ͢Δͱྑ͍ • ྫͰ͸UserΛஔ͖׵͍͑ͯΔ • notifyItemRangeRemoved •

    notifyItemRangeInserted
  12. areContentsTheSame • ΞΠςϜͷத਎͕ಉ͔͡൑ఆ • areItemsTheSame͕trueͷͱ͖ධՁ • ྫͰ͸ImageUrl / isFavoriteΛมߋ •

    notifyItemRangeChanged
  13. DiffUtil.ItemCallback public abstract static class ItemCallback<T> { public abstract boolean

    areItemsTheSame(@NonNull T oldItem, @NonNull T newItem); public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem); public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) { return null; } }
  14. getChangePayload • areItemsTheSame͕true͔ͭ
 areContentsTheSame͕falseͰݺ͹ΕΔ • มߋʹؔ͢ΔϖΠϩʔυΛΦϒδΣΫτͰฦͤΔ • ࡉ͔͍ߋ৽ͷ੍ޚ͕Մೳ

  15. ྫʣUserDiff.kt object UserDiff : DiffUtil.ItemCallback<User>() { override fun areItemsTheSame(oldItem: User,

    newItem: User): Boolean { return oldItem.id "== newItem.id } override fun areContentsTheSame(oldItem: User, newItem: User): Boolean { return oldItem "== newItem } … } data class User( val id: String, val imageUrl: String, val isFavorite: Boolean )
  16. ྫʣUserDiff.kt object UserDiff : DiffUtil.ItemCallback<User>() { … override fun getChangePayload(oldItem:

    User, newItem: User): Any? { return when { oldItem.imageUrl "!= newItem.imageUrl "-> Payload.ImageUrl(newItem.imageUrl) oldItem.isFavorite "!= newItem.isFavorite "-> Payload.IsFavorite(newItem.isFavorite) else "-> null } } sealed class Payload { data class ImageUrl(val value: String) : Payload() data class IsFavorite(val value: Boolean) : Payload() } }
  17. ListAdapterΛ࢖ͬͨ৔߹ class UserListAdapter() : ListAdapter<User, UserViewHolder>(UserDiff) { override fun onCreateViewHolder(parent:

    ViewGroup, viewType: Int): UserViewHolder { … return UserViewHolder(…) } override fun onBindViewHolder(holder: UserViewHolder, position: Int) { holder.bind(getItem(position)) } … }
  18. ListAdapterΛ࢖ͬͨ৔߹ class UserListAdapter() : ListAdapter<User, UserViewHolder>(UserDiff) { … override fun

    onBindViewHolder(holder: UserViewHolder, position: Int, payloads: MutableList<Any>) { if (payloads.isEmpty()) { onBindViewHolder(holder, position) } else { payloads.distinct().forEach { payload "-> when (payload) { is UserDiff.Payload.ImageUrl "-> holder.updateImageUrl(payload.value) is UserDiff.Payload.IsFavorite "-> holder.updateFavorite(payload.value) } } } } }
  19. ListAdapterΛ࢖ͬͨ৔߹ class UserViewHolder(binding: ItemDummyBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(user: User)

    { "// ViewʹUser৘ใΛදࣔ val binding = ItemDummyBinding.bind(itemView) binding.uuid.text = user.id loadImage(user.imageUrl) setImageRes(user.isFavorite) } fun updateImageUrl(imageUrl: String) = loadImage(imageUrl) fun updateFavorite(isFavorite: Boolean) = setImageRes(isFavorite) … }
  20. w BGUFS isFavoriteมߋͰը૾͕ͪΒ͔ͭͳ͍ • before

  21. ͓·͚ɿGroupieΛར༻͍ͯ͠Δ৔߹ • Item#isSameAs • areItemsTheSame • Item#hasSameContentAs • areContentsTheSame •

    Item#getChangePayload • getChangePayload
  22. ·ͱΊ • DiffUtilΛ࢖͏͜ͱͰࠩ෼ߋ৽ָ͕ʹͳΔ • getChangePayloadΛ࢖͏ͱࡉ੍͔͘ޚՄೳ

  23. Appendix • https:"//developer.android.com/guide/topics/ui/layout/recyclerview • https:"//developer.android.com/reference/androidx/recyclerview/widget/DiffUtil • https:"//medium.com/tech-insider/diffutil-handling-recyclerview-smartly- ac3401d22903 • https:"//satoshun.github.io/2019/11/groupie-diffutil/

    • https:"//github.com/DroidKaigi/conference-app-2020/pull/331 • https:"//github.com/rmakiyama/recyclerview-playground