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.

Classes

The Paging Library provides the following classes, as well as additional supporting classes:

DataSource

Use this 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 two subclasses:

  • Use KeyedDataSource 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 one comment to get the contents of the next comment.
  • Use TiledDataSource 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, like "Return the 20 data items beginning with location 1200".

If you use the Room persistence library to manage your data, it can create a TiledDataSource for you automatically, for example:

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

This 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.

PagedListAdapter

This 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.

LivePagedListProvider

This class generates a LiveData<PagedList> from the DataSource you provide. Furthermore, if you use the Room persistence library to manage your database, the DAO can generate the LivePagedListProvider for you, using TiledDataSource, for example:

@Query("SELECT * from users order WHERE age > :age order by name DESC, id ASC")
public abstract LivePagedListProvider<Integer, User> usersOlderThan(int age);

The Integer parameter tells Room to use TiledDataSource with position-based loading under the hood.

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 LivePagedListProvider 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.

The following code sample 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 {
    @Query("SELECT * FROM user ORDER BY lastName ASC")
    public abstract LivePagedListProvider<Integer, User> usersByLastName();
}

class MyViewModel extends ViewModel {
    public final LiveData<PagedList<User>> usersList;
    public MyViewModel(UserDao userDao) {
        usersList = userDao.usersByLastName().create(
                /* initial load position */ 0,
                new PagedList.Config.Builder()
                        .setPageSize(50)
                        .setPrefetchDistance(50)
                        .build());
    }
}

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

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 DiffCallback<User> DIFF_CALLBACK = new DiffCallback<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);
        }
    }
}
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!

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.
(Sep 2017 survey)