Skip to content

Most visited

Recently visited

navigation

Paging Library

The paging library makes it easier for your app to gradually load information as needed from a data source, without overloading the device or waiting too long for a big database query.

Overview

Many apps work with large sets of data, but only need to load and display a small portion of that data at any time. An app might have thousands of items that it could potentially display, but it might only need access to a few dozen of them at once. If the app isn't careful, it can end up requesting data it doesn't actually need, placing a performance burden on the device and the network. If the data is stored or synchronized with a remote database, this can also slow the app and waste the user's data plan.

While existing Android APIs allowed for paging in content, they came with significant constraints and drawbacks:

The new paging library addresses these issues. This library contains several classes to streamline the process of requesting data as you need it. These classes also work seamlessly with existing architecture components, like Room.

Capabilities

The Paging Library's set of classes allow you to complete the tasks shown in this section.

Define how to fetch data

Use the DataSource class to define a data source you need to pull paged data from. Depending on how you need to access your data, you would extend one of its subclasses:

If you use the Room persistence library to manage your data, it can generate a DataSource.Factory to produce instances of PositionalDataSource for you automatically. For example:

@Query("select * from users WHERE age > :age order by name DESC, id ASC")
DataSource.Factory<Integer, User> usersOlderThan(int age);

Load data into memory

The PagedList class loads data from a DataSource. You can configure how much data is loaded at a time, and how much data should be prefetched, minimizing the amount of time your users have to wait for data to be loaded. This class can provide update signals to other classes, such as RecyclerView.Adapter, allowing you to update your RecyclerView's contents as the data is loaded in pages.

Depending on your app's architecture, you have several options for using the PagedList class. To learn more, see Choose a data-loading architecture.

Present data in your UI

The PagedListAdapter class is an implementation of RecyclerView.Adapter that presents data from a PagedList. For example, when a new page is loaded, the PagedListAdapter signals the RecyclerView that the data has arrived; this lets the RecyclerView replace any placeholders with the actual items, performing the appropriate animation.

The PagedListAdapter also uses a background thread to compute changes from one PagedList to the next (for example, when a database change produces a new PagedList with updated data), and calls the notifyItem…() methods as needed to update the list's contents. RecyclerView then performs the necessary changes. For example, if an item changes position between PagedList versions, the RecyclerView animates that item moving to the new location in the list.

Observe data updates

The Paging Library offers the following classes for constructing PagedList containers that are capable of real-time updates:

Create a data flow

Together, the components of the Paging Library organize a data flow from a background thread producer, and presentation on the UI thread. For example, when a new item is inserted in your database, the DataSource is invalidated, and the LiveData<PagedList> or Flowable<PagedList> produces a new PagedList on a background thread.

Figure 1. The Paging Library components do most of their work in a background thread, so they don't burden the UI thread.

That newly-created PagedList is sent to the PagedListAdapter on the UI thread. The PagedListAdapter then uses DiffUtil on a background thread to compute the difference between the current list and the new list. When the comparison is finished, the PagedListAdapter uses the list difference information to make appropriate call to RecyclerView.Adapter.notifyItemInserted() to signal that a new item was inserted.

The RecyclerView on the UI thread then knows that it only has to bind a single new item, and animate it appearing on screen.

Database example

The following code snippets show several possible ways of having all the pieces work together.

Observing paged data using LiveData

The following code snippet shows all the pieces working together. As users are added, removed, or changed in the database, the RecyclerView's content is automatically and efficiently updated:

@Dao
interface UserDao {
    // The Integer type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM user ORDER BY lastName ASC")
    public abstract DataSource.Factory<Integer, User> usersByLastName();
}

class MyViewModel extends ViewModel {
    public final LiveData<PagedList<User>> usersList;
    public MyViewModel(UserDao userDao) {
        usersList = new LivePagedListBuilder<>(
                userDao.usersByLastName(), /* page size */ 20).build();
    }
}

class MyActivity extends AppCompatActivity {
    private UserAdapter<User> mAdapter;

    @Override
    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        RecyclerView recyclerView = findViewById(R.id.user_list);
        mAdapter = new UserAdapter();
        viewModel.usersList.observe(this, pagedList ->
                mAdapter.submitList(pagedList));
        recyclerView.setAdapter(mAdapter);
    }
}

class UserAdapter extends PagedListAdapter<User, UserViewHolder> {
    public UserAdapter() {
        super(DIFF_CALLBACK);
    }
    @Override
    public void onBindViewHolder(UserViewHolder holder, int position) {
        User user = getItem(position);
        if (user != null) {
            holder.bindTo(user);
        } else {
            // Null defines a placeholder item - PagedListAdapter will automatically invalidate
            // this row when the actual object is loaded from the database
            holder.clear();
        }
    }
    public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<User>() {
        @Override
        public boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // User properties may have changed if reloaded from the DB, but ID is fixed
            return oldUser.getId() == newUser.getId();
        }
        @Override
        public boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // NOTE: if you use equals, your object must properly override Object#equals()
            // Incorrectly returning false here will result in too many animations.
            return oldUser.equals(newUser);
        }
    }
}

Observing paged data using RxJava2

If you prefer using RxJava2 instead of LiveData, you can instead create an Observable or Flowable object:

class MyViewModel extends ViewModel {

    public final Flowable<PagedList<User>> usersList;

    public MyViewModel(UserDao userDao) {
        usersList = new RxPagedListBuilder<>(userDao.usersByLastName(),
                /* page size */ 50).buildFlowable(BackpressureStrategy.LATEST);
    }
}

You can then start and stop observing the data using the code in the following snippet:

class MyActivity extends AppCompatActivity {
    private UserAdapter<User> mAdapter;
    private final CompositeDisposable mDisposable = new CompositeDisposable();

    @Override
    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        RecyclerView recyclerView = findViewById(R.id.user_list);
        mAdapter = new UserAdapter();
        recyclerView.setAdapter(mAdapter);
    }

    @Override
    protected void onStart() {
        super.onStart();
        myDisposable.add(mViewModel.usersList.subscribe(flowableList ->
                mAdapter.submitList(flowableList)));
    }

    @Override
    protected void onStop() {
        super.onStop();
        mDisposable.clear();
    }
}

The code for the UserDao and UserAdapter are the same for an RxJava2-based solution as they are for a LiveData-based solution.

Choose a data-loading architecture

There are two primary ways to page data with the Paging Library:

Network or database

First, you can page from a single source - either local storage or network. In this case, use a LiveData<PagedList> to feed loaded data into the UI, such as in the above sample.

To specify your source of data, pass a DataSource.Factory to LivePagedListBuilder.

Figure 2. Single source of data provides DataSource.Factory to load content.

When observing a database, the database will ‘push’ a new PagedList when content changes occur. In network paging cases (when the backend doesn’t send updates), a signal such as swipe-to-refresh can ‘pull’ a new PagedList by invalidating the current DataSource. This refreshes all of the data asynchronously.

The memory + network Repository implementations in the PagingWithNetworkSample show how to implement a network DataSource.Factory using Retrofit while handling swipe-to-refresh, network errors, and retry.

Network and database

In the second case, you may page from local storage, which itself pages additional data from the network. This is often done to minimize network loads and provide a better low-connectivity experience - the database is used as a cache of data stored in the backend.

In this case, use a LiveData<PagedList> to page content from the database, and pass a BoundaryCallback to the LivePagedListBuilder to observe out-of-data signals.

Figure 3. Database is cache of network data - UI loads data from Database, and sends signals when out of data to load from network to database.

Then connect these callbacks to network requests, which will store the data directly in the database. The UI is subscribed to database updates, so new content flows automatically to any observing UI.

The database + network Repository in the PagingWithNetworkSample shows how to implement a network BoundaryCallback using Retrofit, while handling swipe-to-refresh, network errors, and retry.

This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience. (April 2018 — Developer Survey)