Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to notify Dataset changed for RecyclerView

How to notify Dataset changed for RecyclerView

Think about a better way to notify dataset changed for RecyclerView.

51599b694b52e34258f54a98257036b9?s=128

Ryota Takemoto

February 27, 2018
Tweet

Transcript

  1. Ryota Niinomi How to notify Dataset changed for RecyclerView

  2. Ryota Niinomi @AWA @r21nomi r21nomi Android Developer / Creative Coder

    / Anime Lover
  3. What I talk today Think about a better way to

    notify dataset changed
  4. Agenda • Adapter.notify~() • DiffUtil • Paging Library

  5. Adapter.notify~()

  6. • notifyDataSetChanged() • notifyItemRange~() • notifyItem~() Adapter.notify~()

  7. notifyDataSetChanged() • Easy • Redraw all • No animation Adapter.notify~()

  8. notifyItemRange~() • Redraw target items • Animation • Not only

    target items notifyItemRange~()
  9. Not only target items Adapter.notify~() A B C D B,C

    is also redrawn
  10. notifyItem~() • Simple use of notifyItemRange~() • A bit messy

    Adapter.notify~()
  11. notifyItem~() adapter.getDataSet().removeAt(position) adapter.notifyItemRemoved(position) // ok adapter.getDataSet().removeAt(position2) adapter.notifyItemRemoved(position2) // Unintended item

    is removed
  12. adapter.getDataSet().removeAt(position) adapter.notifyItemRemoved(position) adapter.notifyItemRangeChanged( position, adapter.itemCount - position ) adapter.getDataSet().removeAt(position2) adapter.notifyItemRemoved(position2)

    adapter.notifyItemRangeChanged(…) notifyItem~()
  13. adapter.getDataSet().removeAt(position) adapter.notifyItemRemoved(position) adapter.notifyItemRangeChanged( position, adapter.itemCount - position ) // ok

    adapter.getDataSet().removeAt(position2) adapter.notifyItemRemoved(position2) adapter.notifyItemRangeChanged(…) // ok notifyItem~()
  14. Ideal • Easy to use • Redraw only changed items

    • Multiple item notifying • Animation Adapter.notify~()
  15. DiffUtil

  16. DiffUtil • Support Lib 25.1.0~ • Calculate diff between 2

    list • Notify only changed items • Animation DiffUtil
  17. Implementation

  18. Implementation For DiffUtil • DiffUtil.Callback • DiffUtil.calculateDiff() • diffResult.dispatchUpdatesTo()

  19. DiffUtil.Callback override fun getOldListSize() = oldList.size override fun getNewListSize() =

    newList.size // Same item? override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean { return oldList[oldPos].id == newList[newPos].id } // Any changes? override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean { val old = oldList[oldPos] val new = newList[newPos] return old == new } Implementation For DiffUtil
  20. DiffUtil.Callback true false false true areItemsTheSame areContentsTheSame Do not redraw

    redraw
  21. Calculate Diff val diffResult = DiffUtil.calculateDiff(diffCallback) Implementation For DiffUtil

  22. Notify val diffResult = DiffUtil.calculateDiff(diffCallback) diffResult.dispatchUpdatesTo(adapter) Implementation For DiffUtil

  23. Full Implementation val diffCallback = object : DiffUtil.Callback() { val

    oldList = currentDataSet val newList = newDataSet override fun getOldListSize() = oldList.size override fun getNewListSize() = newList.size override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean = oldList[oldPos].id == newList[newPos].id override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean = oldList[oldPos] == newList[newPos] } val diffResult = DiffUtil.calculateDiff(diffCallback) diffResult.dispatchUpdatesTo(adapter)
  24. old list new list

  25. assistant-sdk-python android-audio-high-perfor gmail-add-ons-samples apps-script-oauth2 android-CustomChoiceList android-ndk Log of onBindViewHolder() old

    list new list
  26. assistant-sdk-python android-audio-high-perfor gmail-add-ons-samples apps-script-oauth2 android-CustomChoiceList android-ndk Log of onBindViewHolder() old

    list new list are not redrawn
  27. Calculate diff on background “If the lists are large, this

    operation may take significant time” “run this on a background thread, get the DiffUtil.DiffResult then apply it on the main thread” Implementation For DiffUtil
  28. Calculate diff on background Thread({ val diffCallback = object :

    DiffUtil.Callback() { … } // Calculate diff on background thread. val diffResult = DiffUtil.calculateDiff(diffCallback) handler.post { // Update on main thread. diffResult.dispatchUpdatesTo(adapter) } }).start()
  29. Calculate diff on background Thread({ val diffCallback = object :

    DiffUtil.Callback() { … } // Calculate diff on background thread. val diffResult = DiffUtil.calculateDiff(diffCallback) handler.post { // Update on main thread. diffResult.dispatchUpdatesTo(adapter) } }).start()
  30. Calculate diff on background Thread({ val diffCallback = object :

    DiffUtil.Callback() { … } // Calculate diff on background thread. val diffResult = DiffUtil.calculateDiff(diffCallback) handler.post { // Update on main thread. diffResult.dispatchUpdatesTo(adapter) } }).start()
  31. Paging Library

  32. Paging Library Paging Library • Part of AAC • Having

    same feature as DiffUtil • Paging
  33. Data Flow Paging Library

  34. Structure Paging Library

  35. Implementation

  36. Specification Implementation for Paging Library • Getting data from Github

    API • Sort • Paging
  37. DataSource Implementation for Paging Library • Source of paged data

    • Database, On Memory, API
  38. class PageKeyedRepoDataSource: PageKeyedDataSource<Int, Repo>() { override fun loadInitial(params: LoadInitialParams<Int>, callback:

    LoadInitialCallback<Int, Repo>) { // Initial data fetching } override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Repo>) { // no-op } override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Repo>) { // Paged data fetching } }
  39. override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Repo>) { val page

    = 1 val nextPageKey = 2 val response = repoApiClient .getRepos(USER_NAME, page, params.requestedLoadSize) .execute() callback.onResult(response.body(), null, nextPageKey) }
  40. override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Repo>) { val page

    = params.key val nextPageKey = params.key + 1 val response = repoApiClient .getRepos(USER_NAME, page, params.requestedLoadSize) .execute() callback.onResult(response.body(), nextPageKey) }
  41. DataSource.Factory Implementation for Paging Library • Factory class return DataSource

  42. class RepoDataFactory: DataSource.Factory<Int, Repo> { override fun create(): DataSource<Int, Repo>

    { return object : PageKeyedDataSource<Int, Repo>() { ... } } }
  43. LivePagedListBuilder Implementation for Paging Library • Return PagedList as LiveData

  44. val dataSourceFactory = RepoDataFactory(repoApiClient) val config = PagedList.Config.Builder() .setInitialLoadSizeHint(10) .setPageSize(10)

    .build() return LivePagedListBuilder(dataSourceFactory, config) .setBackgroundThreadExecutor(NETWORK_IO) .build()
  45. private fun getRepos(): LiveData<PagedList<Repo>> { ... } getRepos() .observe(context, Observer

    { pagedList -> // Items are drawn pagedListAdapter.setList(pagedList) }) Observe PagedList
  46. Update DataSet Implementation for Paging Library • Re-fetch API with

    sort condition • Update items only changed by setting PagedList again
  47. private fun getRepos(condition: SortCondition) : LiveData<PagedList<Repo>> { ... } getRepos(SortCondition.UPDATED)

    .observe(context, Observer { pagedList -> // Only new items are drawn repoAdapter.setList(pagedList) })
  48. private fun getRepos(condition: SortCondition) : LiveData<PagedList<Repo>> { ... } getRepos(SortCondition.UPDATED)

    .observe(context, Observer { pagedList -> repoAdapter.setList(null) // All new items are redrawn repoAdapter.setList(pagedList) })
  49. More DataSource Implementation for Paging Library • API • Room

    • Room + API • Other ← This time
  50. Conclusion

  51. Conclusion • Consider the cost of redrawing • Use DiffUtil

    • Paging Library might be better
  52. https://github.com/r21nomi/RecyclerView-DiffUtil-Sample r21nomi/RecyclerView-DiffUtil-Sample

  53. Thank you @r21nomi r21nomi