Slide 1

Slide 1 text

Ryota Niinomi How to notify Dataset changed for RecyclerView

Slide 2

Slide 2 text

Ryota Niinomi @AWA @r21nomi r21nomi Android Developer / Creative Coder / Anime Lover

Slide 3

Slide 3 text

What I talk today Think about a better way to notify dataset changed

Slide 4

Slide 4 text

Agenda • Adapter.notify~() • DiffUtil • Paging Library

Slide 5

Slide 5 text

Adapter.notify~()

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Not only target items Adapter.notify~() A B C D B,C is also redrawn

Slide 10

Slide 10 text

notifyItem~() • Simple use of notifyItemRange~() • A bit messy Adapter.notify~()

Slide 11

Slide 11 text

notifyItem~() adapter.getDataSet().removeAt(position) adapter.notifyItemRemoved(position) // ok adapter.getDataSet().removeAt(position2) adapter.notifyItemRemoved(position2) // Unintended item is removed

Slide 12

Slide 12 text

adapter.getDataSet().removeAt(position) adapter.notifyItemRemoved(position) adapter.notifyItemRangeChanged( position, adapter.itemCount - position ) adapter.getDataSet().removeAt(position2) adapter.notifyItemRemoved(position2) adapter.notifyItemRangeChanged(…) notifyItem~()

Slide 13

Slide 13 text

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~()

Slide 14

Slide 14 text

Ideal • Easy to use • Redraw only changed items • Multiple item notifying • Animation Adapter.notify~()

Slide 15

Slide 15 text

DiffUtil

Slide 16

Slide 16 text

DiffUtil • Support Lib 25.1.0~ • Calculate diff between 2 list • Notify only changed items • Animation DiffUtil

Slide 17

Slide 17 text

Implementation

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

DiffUtil.Callback true false false true areItemsTheSame areContentsTheSame Do not redraw redraw

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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)

Slide 24

Slide 24 text

old list new list

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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()

Slide 29

Slide 29 text

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()

Slide 30

Slide 30 text

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()

Slide 31

Slide 31 text

Paging Library

Slide 32

Slide 32 text

Paging Library Paging Library • Part of AAC • Having same feature as DiffUtil • Paging

Slide 33

Slide 33 text

Data Flow Paging Library

Slide 34

Slide 34 text

Structure Paging Library

Slide 35

Slide 35 text

Implementation

Slide 36

Slide 36 text

Specification Implementation for Paging Library • Getting data from Github API • Sort • Paging

Slide 37

Slide 37 text

DataSource Implementation for Paging Library • Source of paged data • Database, On Memory, API

Slide 38

Slide 38 text

class PageKeyedRepoDataSource: PageKeyedDataSource() { override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { // Initial data fetching } override fun loadBefore(params: LoadParams, callback: LoadCallback) { // no-op } override fun loadAfter(params: LoadParams, callback: LoadCallback) { // Paged data fetching } }

Slide 39

Slide 39 text

override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) { val page = 1 val nextPageKey = 2 val response = repoApiClient .getRepos(USER_NAME, page, params.requestedLoadSize) .execute() callback.onResult(response.body(), null, nextPageKey) }

Slide 40

Slide 40 text

override fun loadAfter(params: LoadParams, callback: LoadCallback) { val page = params.key val nextPageKey = params.key + 1 val response = repoApiClient .getRepos(USER_NAME, page, params.requestedLoadSize) .execute() callback.onResult(response.body(), nextPageKey) }

Slide 41

Slide 41 text

DataSource.Factory Implementation for Paging Library • Factory class return DataSource

Slide 42

Slide 42 text

class RepoDataFactory: DataSource.Factory { override fun create(): DataSource { return object : PageKeyedDataSource() { ... } } }

Slide 43

Slide 43 text

LivePagedListBuilder Implementation for Paging Library • Return PagedList as LiveData

Slide 44

Slide 44 text

val dataSourceFactory = RepoDataFactory(repoApiClient) val config = PagedList.Config.Builder() .setInitialLoadSizeHint(10) .setPageSize(10) .build() return LivePagedListBuilder(dataSourceFactory, config) .setBackgroundThreadExecutor(NETWORK_IO) .build()

Slide 45

Slide 45 text

private fun getRepos(): LiveData> { ... } getRepos() .observe(context, Observer { pagedList -> // Items are drawn pagedListAdapter.setList(pagedList) }) Observe PagedList

Slide 46

Slide 46 text

Update DataSet Implementation for Paging Library • Re-fetch API with sort condition • Update items only changed by setting PagedList again

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

private fun getRepos(condition: SortCondition) : LiveData> { ... } getRepos(SortCondition.UPDATED) .observe(context, Observer { pagedList -> repoAdapter.setList(null) // All new items are redrawn repoAdapter.setList(pagedList) })

Slide 49

Slide 49 text

More DataSource Implementation for Paging Library • API • Room • Room + API • Other ← This time

Slide 50

Slide 50 text

Conclusion

Slide 51

Slide 51 text

Conclusion • Consider the cost of redrawing • Use DiffUtil • Paging Library might be better

Slide 52

Slide 52 text

https://github.com/r21nomi/RecyclerView-DiffUtil-Sample r21nomi/RecyclerView-DiffUtil-Sample

Slide 53

Slide 53 text

Thank you @r21nomi r21nomi