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.

Ryota Takemoto

February 27, 2018
Tweet

More Decks by Ryota Takemoto

Other Decks in Programming

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