Paging library data components and considerations

This guide builds upon the Paging Library overview, discussing how you can customize your app's data-loading solution to meet your app's architecture needs.

Construct an observable list

Typically, your UI code observes a LiveData<PagedList> object (or, if you're using RxJava2, a Flowable<PagedList> or Observable<PagedList> object), which resides in your app's ViewModel. This observable object forms a connection between the presentation and contents of your app's list data.

In order to create one of these observable PagedList objects, pass in an instance of DataSource.Factory to a LivePagedListBuilder or RxPagedListBuilder object. A DataSource object loads pages for a single PagedList. The factory class creates new instances of PagedList in response to content updates, such as database table invalidations and network refreshes. The Room persistence library can provide DataSource.Factory objects for you, or you can build your own.

The following code snippet shows how to create a new instance of LiveData<PagedList> in your app's ViewModel class using Room's DataSource.Factory-building capabilities:

ConcertDao.kt

interface ConcertDao {
    // The Integer type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM concerts ORDER BY date DESC")
    public abstract DataSource.Factory<Integer, Concert> concertsByDate()
}

ConcertViewModel.kt

// The Integer type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Integer, Concert> =
       concertDao.concertsByDate()

val myPagedList = LivePagedListBuilder(myConcertDataSource, /* page size */ 20)
        .build()

Define your own paging configuration

To further configure a LiveData<PagedList> for advanced cases, you can also define your own paging configuration. In particular, you can define the following attributes:

  • Page size: The number of items in each page.
  • Prefetch distance: Given the last visible item in an app's UI, the number of items beyond this last item that the Paging Library should attempt to fetch in advance. This value should be several times larger than the page size.
  • Placeholder presence: Determines whether the UI displays placeholders for list items that haven't finished loading yet. For a discussion about the benefits and drawbacks of using placeholders, learn how to Provide placeholders in your UI.

If you'd like more control over when the Paging Library loads a list from your app's database, pass a custom Executor object to the LivePagedListBuilder, as shown in the following code snippet:

EventViewModel.kt

val myPagingConfig = PagedList.Config.Builder()
        .setPageSize(50)
        .setPrefetchDistance(150)
        .setEnablePlaceholders(true)
        .build()

// The Integer type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Integer, Concert> =
        concertDao.concertsByDate()

val myPagedList = LivePagedListBuilder(myConcertDataSource, myPagingConfig)
        .setFetchExecutor(myExecutor)
        .build()

Choose the correct data source type

It's important to connect to the data source that best handles your source data's structure:

  • Use PageKeyedDataSource if pages you load embed next/previous keys. For example, if you're fetching social media posts from the network, you may need to pass a nextPage token from one load into a subsequent load.
  • Use ItemKeyedDataSource if you need to use data from item N to fetch item N+1. For example, if you're fetching threaded comments for a discussion app, you might need to pass the ID of the last comment to get the contents of the next comment.
  • Use PositionalDataSource if you need to fetch pages of data from any location you choose in your data store. This class supports requesting a set of data items beginning from whatever location you select. For example, the request might return the 20 data items beginning with location 1200.

Notify when data is invalid

When using the Paging Library, it's up to the data layer to notify the other layers of your app when a table or row has become stale. To do so, call invalidate() from the DataSource class that you've chosen for your app.

Build your own data sources

If you use a custom local data solution, or if you load data directly from a network, you can implement one of the DataSource subclassses, as shown in the following code snippet:

class MyDataSource : ItemKeyedDataSource<String, Item>() {
    override fun getKey(item: Item) = item.name

    override fun loadInitial(
            params: LoadInitialParams<string>,
            callback: LoadInitialCallback<Item>) {
        val items = fetchItems(params.requestedLoadSize)
        callback.onResult(items)
    }

    override fun loadAfter(
            params: LoadParams<String>,
            callback: LoadCallback<Item>) {
        val items = fetchItemsAfter(
            start = params.key,
            limit = params.requestedLoadSize)
        callback.onResult(items)
    }
}

Consider how content updates work

As you construct observable PagedList objects, consider how content updates work. If you're loading data directly from a Room database updates get pushed to your app's UI automatically.

If you are using a paged network API, you typically have a user interaction, such as "swipe to refresh", serve as a signal for invalidating your current DataSource and requesting a new one. This behavior appears in the following code snippet:

class ConcertActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        concertViewModel.refreshState.observe(this, Observer {
            swipeRefreshLayout.isRefreshing =
                    it == NetworkState.LOADING
        })
        swipeRefreshLayout.setOnRefreshListener {
            concertViewModel.invalidateDataSource()
        }
    }
}

Provide mapping between data representations

The Paging Library supports item-based and page-based transformations of items loaded by a DataSource.

In the following code snippet, a combination of concert name and concert date is mapped to a single string containing both the name and date:

class ConcertViewModel : ViewModel() {
    val concertDescriptions : LiveData<PagedList<String>>
        init {
            val factory = database.allConcertsFactory()
                    .map { concert ->
                           concert.name + " - " + concert.date
                    }
            concerts = LivePagedListBuilder(factory, 30).build()
        }
    }
}

This can be useful if you want to wrap, convert, or prepare items after they're loaded. Because this work is done on the fetch executor, you can do potentially expensive work, such as reading from disk or querying a separate database.