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

Paging Library in Action by Graeme Campbell

Paging Library in Action by Graeme Campbell

A new feature in the Android Architecture Components  / Android Jetpack

GDG Montreal

June 20, 2018
Tweet

More Decks by GDG Montreal

Other Decks in Technology

Transcript

  1. Paging Library in Action A new feature in the Android

    Architecture Components / Android Jetpack Graeme Campbell,
  2. What is Paging? • Load large data sets, in small

    chunks • Saves bandwidth • Saves battery • Faster • Not a new concept: • CursorAdapter / ListView • 3rd party Libraries • Custom
  3. Android Jetpack: Paging Library! • Combines RecyclerView, ViewModel, and LiveData

    • PagedListAdapter • ViewModel has a LiveData<PagedList> • Highly flexible • Automatic with Room database • Custom DataSource: Network, Files, or any other source • Also works with RxJava • Uses Observable<PagedList> • Uses Flowable<PagedList> • Skips LiveData
  4. Repository Pattern • Layer between ViewModel and DataSouce • Multiple

    sources of data • Save / Transform / Combine data Sample projects: https://codelabs.developers.google.com/codelabs/android-paging/index.html https://github.com/googlesamples/android-architecture-components/tree/master/PagingWithNetworkSample
  5. Main UI Components • PagedListAdapter • Extends RecyclerView.Adapter • Normal

    ViewHolder • Constructor needs data entity and DiffUtil comparator class ReposAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() … class ReposAdapter : PagedListAdapter<RepoEntity, RecyclerView.ViewHolder>(REPO_COMPARATOR) • PagedList • Extends AbstractList<E> • Basically, ArrayList with extra features
  6. Extend PagedListAdapter class ReposAdapter : PagedListAdapter<RepoEntity, RecyclerView.ViewHolder>(REPO_COMPARATOR) { override fun

    onCreateViewHolder… override fun onBindViewHolder… companion object { private val REPO_COMPARATOR = object : DiffUtil.ItemCallback< RepoEntity>() { override fun areItemsTheSame(oldItem: RepoEntity, newItem: RepoEntity): Boolean = oldItem.fullName == newItem.fullName override fun areContentsTheSame(oldItem: RepoEntity, newItem: RepoEntity): Boolean = oldItem == newItem } } }
  7. Add PagedListAdapter to Activity class SearchRepositoriesActivity : AppCompatActivity() { lateinit

    var viewModel: SearchRepositoriesViewModel val adapter = ReposAdapter() override fun onCreate(savedInstanceState: Bundle?) { … viewModel = ViewModelProviders.of(this).get(SearchRepositoriesViewModel::class.java) initAdapter() } private fun initAdapter() { list.adapter = adapter viewModel.repos.observe(this, Observer<PagedList<RepoEntity>> { // update adapter with PagedList results adapter.submitList(it) })
  8. Behind the Scenes: DataSource • Automatic with Room Database •

    Custom DataSources possible. Three different types: • PageKeyedDataSource • Backend sends “Next” and “Previous” links • Or, count items, and send “offset” with next request • ItemKeyedDataSource • Send “After” or “Before” to backend, with Item ID • PositionalDataSource • Entire size of dataset is known • Enables “Placeholders”, and rapid scrolling to arbitrary positions in the dataset • (Room database uses this under the hood) • TouchTunes tried Room • Not enough time to implement Room for full data model • Went with PageKeyedDataSource
  9. DataSource Example #1: Room Database @Dao interface RepoDao { @Insert(onConflict

    = OnConflictStrategy.REPLACE) fun insert(posts: List<RepoEntity>) @Query("SELECT * FROM repos WHERE (name LIKE :queryString) // fun reposByName(queryString: String): LiveData<List<RepoEntity>> fun reposByName(queryString: String): DataSource.Factory<Int, RepoEntity> }
  10. DataSource Example #2: A Custom DataSource class PageKeyedSubredditDataSource() : PageKeyedDataSource<String,

    RedditPost>() { override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<String, RedditPost>) { redditApi.getTop(subredditName, limit = params.requestedLoadSize) callback.onResult(response.items, response.before, response.after) } override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, RedditPost>) { redditApi.getTopAfter(subredditName, params.key, limit = params.requestedLoadSize) callback.onResult(response.items, response.after) } override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, RedditPost>) { // ignored, since we only ever append to our initial load } }
  11. Connect it all: LivePagedListBuilder val sourceFactory = SubRedditDataSourceFactory(redditApi, subReddit) val

    pagedListConfig = PagedList.Config.Builder() .setEnablePlaceholders(false) .setInitialLoadSizeHint(pageSize * 2) .setPageSize(pageSize) .build() val pagedList = LivePagedListBuilder(sourceFactory, pagedListConfig) .build()
  12. A couple more pieces… • DataSouce.Factory • Custom DataSources must

    implement create method: public abstract static class Factory<Key, Value> { public abstract DataSource<Key, Value> create(); • BoundaryCallback • Only for Room DB, must implement 3 methods: public abstract static class BoundaryCallback<T> { public void onZeroItemsLoaded() {} public void onItemAtFrontLoaded(@NonNull T itemAtFront) {} public void onItemAtEndLoaded(@NonNull T itemAtEnd) {}
  13. Before / After Metrics Old-Style Pagination Lines of Code Paging

    Library Lines of Code 2 Custom Views, 1 abstract class 377 1 Custom View 340 Custom RecyclerView 200 Adapters / ViewHolders 234 Adapters / ViewHolders 200 Custom PaginationListener 36 ViewModel 16 Paginated Data Loader 300 Repository 36 Data Presenter / Manager 788 DataSource.Factory 21 DataSource 91 Total 1935 Total 704