In my quest to explore all that Android Jetpack has to offer, I took some time to understand capabilities of the new Paging library introduced at Google I/O ’18. This library can be used to implement endless scrolling (infinite scrolling) for your RecyclerView data. Let’s explore the various ways Paging library can be used by building a Note-taking app in Kotlin.

While paging is the main focus, we will use the following components of Android Jetpack:

Paging Library Room Persistence Library Navigation library Lifecycle (ViewModel, LiveData)

Dagger Android 2.17 is used for dependency injection in most branches. Koin usage is also demonstrated. The app has a decent separation of layers (Clean Architecture) including mapping Model objects between Data and Domain layers.

You can check the source code at https://github.com/jshvarts/PagingRoom.

There are several branches:

master branch that the rest of the repo is built upon. There is no paging implemented here. room-livedata branch that introduces the Paging library in its simplest form ( PositionalDataSource is the default Paging implementation in Room). room-rxjava branch that shows how you would use the Paging library with RxJava. livedata-custom-datasource branch where we will build a custom ItemKeyedDataSource with Dagger. More details on various DataSource types are below. livedata-custom-datasource-koin branch where we will build a custom ItemKeyedDataSource with Koin. More details on various DataSource types are below.

Paging library overview

Paging in a nutshell

Above you see an overview of how Paging library fits into Clean Architecture.

Data layer: Contains implementation of the Domain layer abstractions such as Data Repository.

Domain layer: Contains your repository abstractions for the Data layer to implement. The green boxes here signify optional component — you don’t have to implement DataSource and DataSource.Factory if the built-in implementation already does what you need (in this case, Room comes with default paging implementation of type PositionalDataSource ). It is a great example of how various Architecture Components work together.

Presentation layer: Here you have your ViewModel that produces LiveData<PagedList<T>> . Again, the green boxes signify optional APIs to implement. You can use LivePagedListBuilder or RxPagedListBuilder to produce your LiveData without using DataSource.Factory or having to customize your PagedList.Config .

UI layer: Here you observe your LiveData<PagedList<T>> . Once you have your PagedList, you submit it to the PagedListAdapter which binds your data and adds it to the RecyclerView .

PagedList is a central component to how Paging works. It loads data in pages asynchronously. It’s backed by DataSource which lazily delivers snapshots of data as the PagedList needs it (when user scrolls).

While scrolling, if placeholders are enabled, they are replaced by the actual data. Otherwise, the new PagedList simply gets appended to the currently displayed list based on the last visible item (it works similarly for scrolling up). See more on placeholders below.

Wrapped in LiveData , the PagedList then gets dispatched to the UI. If the data becomes stale (the data in the DataSource was changed while user was looking or scrolling the items), it gets invalidated and the DataSource dispatches new PagedList wrapped in LiveData . Paging library is able to support infinite scrolling while supporting data updates without ever calling the expensive onDataSetChanged() .

DataSource is a wrapper around your sources of data (whether it is a local database such as Room, Realm, SQLite, etc.) or a remote API (usually accessed via Retrofit, Volley, etc.) or a combination of both local and remote sources.

Let’s look at 3 implementations of DataSource :

PositionalDataSource offers position-based data loader for a fixed-size, countable data set. For instance, it allows scrolling through a list of contacts or jumping to a particular position in the list (i.e. jumping to contacts that start with a particular letter). This is how Room implements paging by default under the hood. The repo for this article contains examples of implementing PositionalDataSource with LiveData or PositionalDataSource with RxJava. ItemKeyedDataSource can be used when you can identify items before the start of the current list and after the end of the current list based on particular item key because the keys are ordered. The repo for this article contains an example of implementing ItemKeyedDataSource with LiveData. PageKeyedDataSource is often used for remote API calls to load data in chunks where the response returned will contain the data itself as well as pointers to next and previous pages of data (much like a LinkedList). It reduces the size of the data transfer for a given scroll. Since the total count is usually not available in remote APIs, placeholders should be disabled. More on that later.

Project setup

Let’s go over the code at https://github.com/jshvarts/PagingRoom/tree/room-livedata

We’ll start off with these main dependencies:

Implement the DAO. Note the return type of DataSource.Factory<Int, NoteEntity> where the key is an Int since we are using positions (remember by default Room provides PositionalDataSource ). Why do we return a DataSource.Factory ? A database is something that changes over time and each DataSource represents a snapshot of data at a given time. DataSource.Factory creates those snapshots for us.

Let’s define our database and pre-populate it with 100 items when it’s initially created:

It’s time to add our Repository interface in the Domain layer.

And implement it in the Data layer.

Note that the Domain layer “does not know” about any of the other layers (you can see that based on package imports). In turn, the Data layer implements the Domain layer abstractions. This will come in handy if we decide to swap our database implementation as well as for testability and modularizing our code.

Let’s create a Domain UseCase/Interactor so we can eventually put any business rules there. At the moment, the UseCase does not do anything special but it does hide details about our repository from the Presentation layer. The note listing presentation layer does not need to know all of the things (i.e. inserting or deleting notes) that the repository can do.

It’s time to set up our Presentation layer. Let’s add the ViewModel for loading notes.

The LivePagedListBuilder uses our default DataSource.Factory and PagedList.Config . The only value you must provide to configure your DataSource on the fly is the pageSize . For more flexibility, we could build a custom configuration object PagedList.Config and define a custom DataSource.Factory to pass in place of getNotesUseCase.allNotes() . You will see an example of using a custom DataSource.Factory later. For now let’s look at configuring the PagedList:

Placeholders

Placeholders provide visual indicators for items that have not been loaded yet but will be as soon as the user scrolls to these items. Besides better user experience, a nice benefit of the placeholders is that developers don’t need to implement a loading spinner at the bottom of the list since the users already see loading indicators (such as gray areas for icons) while the actual items are yet to be rendered. Note that placeholders can only be enabled if your DataSource provides total items count.