Advertisement
  1. Code
  2. Coding Fundamentals
  3. Design Patterns

Android Architecture Components: Using the Paging Library With Room

Scroll to top

In this tutorial, I'll show you how to use the Paging library from the Android Architecture Components with a Room-backed database in an Android app. 

You'll learn how to use the Paging library to efficiently load large data sets from a Room-backed database—giving your users a smoother experience while scrolling in a RecyclerView. 

Prerequisites

To be able to follow this tutorial, you'll need:

If you haven't learnt about the architecture components, you are strongly advised to check out our awesome series all about Android Architecture Components by Tin Megali. Make sure you go dive in! 

A sample project for this tutorial can be found on our GitHub repo so you can easily follow along.

What Is the Paging Library?

The Paging library is another library added to the Architecture Components. The library helps efficiently manage the loading and display of a large data set in the RecyclerView. According to the official docs:

The Paging Library makes it easier for you to load data gradually and gracefully within your app's RecyclerView.

If any part of your Android app is going to display a large dataset from either a local or remote data source but displays only part of it at a time, then you should consider using the Paging library. This will help improve the performance of your app! 

So Why Use the Paging Library?

Now that you've seen an introduction to the Paging library, you might ask, why use it? Here are some reasons why you should consider using it in loading large data sets in a RecyclerView

  • It doesn't request data that aren't needed. This library only requests data that are visible to the user—as the user scrolls through the list. 
  • Saves the user's battery and consumes less bandwidth. Because it only requests data that are needed, this saves some device resources. 

It won't be efficient when working with a large amount of data, as the underlying data source retrieves all the data, even though only a subset of that data is going to be displayed to the user. In such a situation, we should consider paging the data instead. 

1. Create an Android Studio Project

Fire up your Android Studio 3 and create a new project with an empty activity called MainActivity. Make sure to check Include Kotlin support

Android Studio create project screenAndroid Studio create project screenAndroid Studio create project screen

2. Add the Architecture Components

After creating a new project, add the following dependencies in your build.gradle. In this tutorial, we are using the latest Paging library version 1.0.1, while Room is 1.1.1 (as of this writing). 

1
dependencies {
2
    implementation fileTree(dir: 'libs', include: ['*.jar'])
3
    implementation "android.arch.persistence.room:runtime:1.1.1"
4
    kapt "android.arch.persistence.room:compiler:1.1.1"
5
    implementation "android.arch.paging:runtime:1.0.1"
6
    implementation "com.android.support:recyclerview-v7:27.1.1"
7
}

These artifacts are available at Google’s Maven repository. 

1
allprojects {
2
    repositories {
3
        google()
4
        jcenter()
5
    }
6
}

By adding the dependencies, we have taught Gradle how to find the library. Make sure you remember to sync your project after adding them. 

3. Create the Entity

Create a new Kotlin data class Person. For simplicity's sake, our Person entity has just two fields:

  • a unique ID (id)
  • the name of the person (name)

In addition, include a toString( method that simply returns the name

1
import android.arch.persistence.room.Entity
2
import android.arch.persistence.room.PrimaryKey
3
4
@Entity(tableName = "persons")
5
data class Person(
6
        @PrimaryKey val id: String,
7
        val name: String
8
) {
9
    override fun toString() = name
10
}

4. Create the DAO

As you know, for us to access our app's data with the Room library, we need data access objects (DAOs). In our own case, we have created a PersonDao

1
import android.arch.lifecycle.LiveData
2
import android.arch.paging.DataSource
3
import android.arch.persistence.room.Dao
4
import android.arch.persistence.room.Delete
5
import android.arch.persistence.room.Insert
6
import android.arch.persistence.room.Query
7
8
@Dao
9
interface PersonDao {
10
11
    @Query("SELECT * FROM persons")
12
    fun getAll(): LiveData<List<Person>>
13
14
    @Query("SELECT * FROM persons")
15
    fun getAllPaged(): DataSource.Factory<Int, Person>
16
17
    @Insert
18
    fun insertAll(persons: List<Person>)
19
20
    @Delete
21
    fun delete(person: Person)
22
}

In our PersonDao class, we have two @Query methods. One of them is getAll(), which returns a LiveData that holds a list of Person objects. The other one is getAllPaged(), which returns a DataSource.Factory

According to the official docs, the DataSource class is the:

Base class for loading pages of snapshot data into a PagedList.

A PagedList is a special kind of List for showing paged data in Android: 

A PagedList is a List which loads its data in chunks (pages) from a DataSource. Items can be accessed with get(int), and further loading can be triggered with loadAround(int).

We called the Factory static method in the DataSource class, which serves as a factory (creating objects without having to specify the exact class of the object that will be created) for the DataSource. This static method takes in two data types:

  • The key that identifies items in DataSource. Note that for a Room query, pages are numbered—so we use Integer as the page identifier type. It is possible to have "keyed" pages using the Paging library, but Room doesn't offer that at present. 
  • The type of items or entities (POJOs) in the list loaded by the DataSources.

5. Create the Database

Here's is what our Room database class AppDatabase looks like: 

1
import android.arch.persistence.db.SupportSQLiteDatabase
2
import android.arch.persistence.room.Database
3
import android.arch.persistence.room.Room
4
import android.arch.persistence.room.RoomDatabase
5
import android.content.Context
6
import androidx.work.OneTimeWorkRequestBuilder
7
import androidx.work.WorkManager
8
import com.chikeandroid.pagingtutsplus.utils.DATABASE_NAME
9
import com.chikeandroid.pagingtutsplus.workers.SeedDatabaseWorker
10
11
@Database(entities = [Person::class], version = 1, exportSchema = false)
12
abstract class AppDatabase : RoomDatabase() {
13
    abstract fun personDao(): PersonDao
14
15
    companion object {
16
17
        // For Singleton instantiation

18
        @Volatile private var instance: AppDatabase? = null
19
20
        fun getInstance(context: Context): AppDatabase {
21
            return instance ?: synchronized(this) {
22
                instance
23
                        ?: buildDatabase(context).also { instance = it }
24
            }
25
        }
26
27
        private fun buildDatabase(context: Context): AppDatabase {
28
            return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
29
                    .addCallback(object : RoomDatabase.Callback() {
30
                        override fun onCreate(db: SupportSQLiteDatabase) {
31
                            super.onCreate(db)
32
                            val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
33
                            WorkManager.getInstance()?.enqueue(request)
34
                        }
35
                    })
36
                    .build()
37
        }
38
    }
39
}

Here we have created a single instance of our database and pre-populated it with data using the new WorkManager API. Note that the data pre-populated is just a list of 1,000 names (dive into the sample source code provided to learn more). 

6. Creating the ViewModel

For our UI to store, observe, and serve data in a lifecycle-conscious way, we need a ViewModel. Our PersonsViewModel, which extends the AndroidViewModel class, is going to function as our ViewModel

1
import android.app.Application
2
import android.arch.lifecycle.AndroidViewModel
3
import android.arch.lifecycle.LiveData
4
import android.arch.paging.DataSource
5
import android.arch.paging.LivePagedListBuilder
6
import android.arch.paging.PagedList
7
import com.chikeandroid.pagingtutsplus.data.AppDatabase
8
import com.chikeandroid.pagingtutsplus.data.Person
9
10
class PersonsViewModel constructor(application: Application)
11
    : AndroidViewModel(application) {
12
13
    private var personsLiveData: LiveData<PagedList<Person>>
14
15
    init {
16
        val factory: DataSource.Factory<Int, Person> =
17
        AppDatabase.getInstance(getApplication()).personDao().getAllPaged()
18
19
        val pagedListBuilder: LivePagedListBuilder<Int, Person>  = LivePagedListBuilder<Int, Person>(factory,
20
                50)
21
        personsLiveData = pagedListBuilder.build()
22
    }
23
24
    fun getPersonsLiveData() = personsLiveData
25
}

In this class, we have a single field called personsLiveData. This field is simply a LiveData that holds a PagedList of Person objects. Because this is a LiveData, our UI (the Activity or Fragment) is going to observe this data by calling the getter method getPersonsLiveData()

We initialized personsLiveData inside the init block. Inside this block, we get the DataSource.Factory by calling the AppDatabase singleton for the PersonDao object. When we get this object, we call getAllPaged()

We then create a LivePagedListBuilder. Here's what the official documentation says about a LivePagedListBuilder

Builder for LiveData<PagedList>, given a DataSource.Factory and a PagedList.Config.

We supply its constructor a DataSource.Factory as the first argument and the page size as the second argument (in our own case, the page size will be 50). Typically, you should choose a size that's higher than the maximum number that you might display at once to the user. In the end, we call build() to construct and return to us a LiveData<PagedList>

7. Creating the PagedListAdapter

To show our PagedList data in a RecyclerView, we need a PagedListAdapter. Here's a clear definition of this class from the official docs:

RecyclerView.Adapter base class for presenting paged data from PagedLists in a RecyclerView.

So we create a PersonAdapter that extends PagedListAdapter.

1
import android.arch.paging.PagedListAdapter
2
import android.content.Context
3
import android.support.v7.widget.RecyclerView
4
import android.view.LayoutInflater
5
import android.view.View
6
import android.view.ViewGroup
7
import android.widget.TextView
8
import com.chikeandroid.pagingtutsplus.R
9
import com.chikeandroid.pagingtutsplus.data.Person
10
import kotlinx.android.synthetic.main.item_person.view.*
11
12
class PersonAdapter(val context: Context) : PagedListAdapter<Person, PersonAdapter.PersonViewHolder>(PersonDiffCallback()) {
13
14
    override fun onBindViewHolder(holderPerson: PersonViewHolder, position: Int) {
15
        var person = getItem(position)
16
17
        if (person == null) {
18
            holderPerson.clear()
19
        } else {
20
            holderPerson.bind(person)
21
        }
22
    }
23
24
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder {
25
        return PersonViewHolder(LayoutInflater.from(context).inflate(R.layout.item_person,
26
                parent, false))
27
    }
28
29
30
    class PersonViewHolder (view: View) : RecyclerView.ViewHolder(view) {
31
32
        var tvName: TextView = view.name
33
34
        fun bind(person: Person) {
35
            tvName.text = person.name
36
        }
37
38
        fun clear() {
39
            tvName.text = null
40
        }
41
42
    }
43
}

PagedListAdapter is used just like any other subclass of RecyclerView.Adapter. In other words, you have to implement the methods onCreateViewHolder() and onBindViewHolder()

To extend the PagedListAdapter abstract class, you will have to supply—in its constructor—the type of PageLists (this should be a plain old Java class: a POJO) and also a class that extends the ViewHolder that will be used by the adapter. In our case, we gave it Person and PersonViewHolder as the first and second argument respectively. 

Note that PagedListAdapter requires you pass it a DiffUtil.ItemCallback to the PageListAdapter constructor. DiffUtil is a RecyclerView utility class that can calculate the difference between two lists and output a list of update operations that converts the first list into the second one. ItemCallback is an inner abstract static class (inside DiffUtil) used for calculating the diff between two non-null items in a list. 

Specifically, we supply PersonDiffCallback to our PagedListAdapter constructor. 

1
import android.support.v7.util.DiffUtil
2
import com.chikeandroid.pagingtutsplus.data.Person
3
4
class PersonDiffCallback : DiffUtil.ItemCallback<Person>() {
5
6
    override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
7
        return oldItem.id == newItem.id
8
    }
9
10
    override fun areContentsTheSame(oldItem: Person?, newItem: Person?): Boolean {
11
        return oldItem == newItem
12
    }
13
}

Because we are implementing DiffUtil.ItemCallback, we have to implement two methods: areItemsTheSame() and areContentsTheSame()

  • areItemsTheSame is called to check whether two objects represent the same item. For example, if your items have unique ids, this method should check their id equality. This method returns true if the two items represent the same object or false if they are different.
  • areContentsTheSame is called to check whether two items have the same data. This method returns true if the contents of the items are the same or false if they are different.

Our PersonViewHolder inner class is just a typical RecyclerView.ViewHolder. It's responsible for binding data as needed from our model into the widgets for a row in our list. 

1
class PersonAdapter(val context: Context) : PagedListAdapter<Person, PersonAdapter.PersonViewHolder>(PersonDiffCallback()) {
2
    
3
    // ... 

4
    
5
    class PersonViewHolder (view: View) : RecyclerView.ViewHolder(view) {
6
7
        var tvName: TextView = view.name
8
9
        fun bind(person: Person) {
10
            tvName.text = person.name
11
        }
12
13
        fun clear() {
14
            tvName.text = null
15
        }
16
17
    }
18
}

8. Showing the Result

In our onCreate() of our MainActivity, we simply did the following:

  • initialize our viewModel field using the utility class ViewModelProviders
  • create an instance of PersonAdapter
  • configure our RecyclerView
  • bind the PersonAdapter to the RecyclerView
  • observe the LiveData and submit the PagedList objects over to the PersonAdapter by invoking submitList()
1
import android.arch.lifecycle.Observer
2
import android.arch.lifecycle.ViewModelProviders
3
import android.os.Bundle
4
import android.support.v7.app.AppCompatActivity
5
import android.support.v7.widget.RecyclerView
6
import com.chikeandroid.pagingtutsplus.adapter.PersonAdapter
7
import com.chikeandroid.pagingtutsplus.viewmodels.PersonsViewModel
8
9
class MainActivity : AppCompatActivity() {
10
11
    private lateinit var viewModel: PersonsViewModel
12
13
    override fun onCreate(savedInstanceState: Bundle?) {
14
        super.onCreate(savedInstanceState)
15
        setContentView(R.layout.activity_main)
16
17
        viewModel = ViewModelProviders.of(this).get(PersonsViewModel::class.java)
18
19
        val adapter = PersonAdapter(this)
20
        findViewById<RecyclerView>(R.id.name_list).adapter = adapter
21
22
        subscribeUi(adapter)
23
    }
24
25
    private fun subscribeUi(adapter: PersonAdapter) {
26
        viewModel.getPersonLiveData().observe(this, Observer { names ->
27
            if (names != null) adapter.submitList(names)
28
        })
29
    }
30
}

Finally, when you run the app, here's the result:

Tutorial result screenshot Tutorial result screenshot Tutorial result screenshot

While scrolling, Room is able to prevent gaps by loading 50 items at a time and making them available to our PersonAdapter, which is a subclass of PagingListAdapter. But note that not all data sources will be loaded quickly. The loading speed also depends on the processing power of the Android device. 

9. Integration With RxJava

If you're using or want to use RxJava in your project, the paging library includes another useful artifact: RxPagedListBuilder. You use this artifact instead of LivePagedListBuilder for RxJava support. 

You simply create an instance of RxPagedListBuilder, supplying the same arguments as you would for LivePagedListBuilder—the DataSource.Factory and the page size. You then call buildObservable() or buildFlowable() to return an Observable or Flowable for your PagedList respectively. 

To explicitly provide the Scheduler for the data loading work, you call the setter method setFetchScheduler(). To also provide the Scheduler for delivering the result (e.g. AndroidSchedulers.mainThread()), simply call setNotifyScheduler(). By default, setNotifyScheduler() defaults to the UI thread, while setFetchScheduler() defaults to the I/O thread pool. 

Conclusion

In this tutorial, you learned how to easily use the Paging component from the Android Architecture Components (which are part of Android Jetpack) with Room. This helps us efficiently load large data sets from the local database to enable a smoother user experience while scrolling through a list in the RecyclerView

I highly recommend checking out the official documentation to learn more about the Paging library in Android.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.