Database queries take a long time to run and use a lot of memory. Android has a new paging library that can help you with all of this.
Main Classes of Paging Library
The main classes of the paging library are PagedListAdapter
, that actually extends the RecyclerViewAdapter
, PagedList
, and DataSource
. DataSource: The DataSource is an interface for page source to provide the data gradually.Read more about different DataSource type use case. If you use the Room persistence library to manage your data, it can generate a
DataSource.Factory
to producePositionalDataSources
for you automatically, for example:
@Query("SELECT * FROM User") public abstract DataSource.Factory<Integer,User> usersByFirstName();
PagedList: The PagedList is a component that loads the data automatically and can provide update signal, for example, to the RecyclerViewAdapter
. The data is loaded automatically from a DataSource on the background thread.But then, it’s consumed on the main thread.It supports both an infinite scrolling list, but also countable lists. You can configure several things.You can configure the initial load size, the page size, but also the prefetch distance. 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.
DataFlow
We have some data that we put on the DataSource in the background thread.The DataSource invalidates the
PagedList
and +updates its value. Then on the main thread, the PagedList
notifies its observers of the new value.Now the PagedListAdapter
knows about the new value. The background thread, the PageListAdapter
needs to compute what’s changed, whats’ the difference and back on the UI thread, the View is updated in the onBinderViewHolder
. So all of this happens automatically.You just insert an item in that database, and then you see it animated in and no UI code is required.
Paging Library Example
1.Adding Components to your Project
Architecture Components are available from Google’s Maven repository. To use them, follow these steps: Open the build.gradle
file for your project and add the line as shown below:
allprojects { repositories { jcenter() maven { url 'https://maven.google.com' } } }
Add Architecture Components
In this tutorial, we are using Room, LiveData, and ViewModel. Open the build.gradle
file for your app or module and add the artifacts that you need as dependencies:
dependencies { .... // ViewModel and LiveData implementation "androidx.lifecycle:lifecycle-extensions:2.0.0-beta01" annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.0.0-beta01" // Room implementation "androidx.room:room-runtime:2.0.0-beta01" annotationProcessor "androidx.room:room-compiler:2.0.0-beta01" // Paging implementation "androidx.paging:paging-runtime:2.0.0-beta01" }
2.Create DataSource
Create Entity
Represents a class that holds a database row. For each entity, a database table is created to hold the items. You must reference the entity class in the Database
class.
@Entity public class User { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "user_id") public long userId; @ColumnInfo(name = "first_name") public String firstName; public String address; ..... }
Data Access Objects (DAO)
To simplify the connection between the DataSource and the RecyclerView, we can use a LivePagedListProvider
.So this will expose, actually, a LiveData
of a PageList
of our user.So all you will need to do is provide a DataSource.But if that DataSource is true, then it will be generated for you in the DAO.You don’t need to write any invalidating handling code.You can simply bind the LiveData
of a PagedList
to a PagedListAdapter
and get updates, invalidates, and lifecycle cleanup with a single line of binding code. So in our user DAO, we would return a LivePagedListProvider of our user to get the users by the last name.
@Dao public interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) public void insertAll(List<User> users); @Query("SELECT * FROM User") public abstract DataSource.Factory<Integer,User> usersByFirstName(); }
Create Database
The annotation defines the list of entities, and the class’s content defines the list of data access objects (DAOs) in the database. It is also the main access point for the underlying connection. The annotated class should be an abstract class that extends RoomDatabase
.
@Database(entities = {User.class}, version = 1) abstract public class AppDatabase extends RoomDatabase { public static final String DATABASE_NAME = "UserDb"; public abstract UserDao userDao(); }
3.Create ViewModel
In the ViewModel, we would extend from the Architecture Component ViewModel, and then we would keep a reference to that LiveData of our PagedList and we will get that reference from the DAO by calling getUsers(), and then call Create using the configuration that you want. So for example, setting the page size to 50, setting the prefetch distance to 50 and so on.
public class UserViewModel extends ViewModel { public LiveData<PagedList<User>> userList; public UserViewModel() { } public void init(UserDao userDao) { PagedList.Config pagedListConfig = (new PagedList.Config.Builder()).setEnablePlaceholders(true) .setPrefetchDistance(10) .setPageSize(20).build(); userList = (new LivePagedListBuilder(userDao.usersByFirstName(), pagedListConfig)) .build(); } }
In the onCreate, we get the reference to our ViewModel.We get the reference to the RecyclerView, and we create our adapter.
RecyclerView recyclerView = findViewById(R.id.userList); LinearLayoutManager llm = new LinearLayoutManager(this); llm.setOrientation(LinearLayoutManager.VERTICAL); recyclerView.setLayoutManager(llm); UserViewModel viewModel = ViewModelProviders.of(this).get(UserViewModel.class); viewModel.init(userDao); final UserAdapter userUserAdapter = new UserAdapter(); viewModel.userList.observe(this, pagedList -> { userUserAdapter.submitList(pagedList); }); recyclerView.setAdapter(userUserAdapter);
4.Create Adapter
To tell the PagedListAdapter how to compute the difference between the two elements, you’ll need to implement a new class, DiffCallback.Here, you will define two things.
@Entity public class User { public static DiffUtil.ItemCallback<User> DIFF_CALLBACK = new DiffUtil.ItemCallback<User>() { @Override public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) { return oldItem.userId == newItem.userId; } @Override public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) { return oldItem.equals(newItem); } }; ..... @Override public boolean equals(Object obj) { if (obj == this) return true; User user = (User) obj; return user.userId == this.userId && user.firstName == this.firstName; } }
You will define how to compute whether the contents are the same, and how to define whether the items are the same. Let’s look at the adapter.So our adapter would extend PagedListAdapter, and then it will connect the user, which is the information that’s being displayed, with the user ViewHolder.
public class UserAdapter extends PagedListAdapter<User, UserAdapter.UserItemViewHolder> { protected UserAdapter() { super(User.DIFF_CALLBACK); } @Override public UserItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); View view = layoutInflater.inflate(R.layout.item_user_list, parent, false); return new UserItemViewHolder(view); } @Override public void onBindViewHolder(UserItemViewHolder holder, int position) { User user= getItem(position); if(user!=null) { holder.bindTo(user); } } }
We define the callback, the DIFF_CALLBACK, for our user objects and then in onBindViewHolder, all we need to do is bind the item to the ViewHolder.That’s all. Download this project from GitHub
Good article about paging in library. I was searching for paging in android but many projects were in Kotlin. As a new commer to Room it was very difficult for me to understand Kotlin and convert it to java.
Your article was good in the sense that it provides step by step guidelines on how to use individual component of paging.
Thanks again.