1. Introduction

In order to solve the loading more functions of RecyclerView, Google launched the Paging component of Jetpack. The Paging component enables more data to be loaded smoothly and seamlessly.

Paging uses a DataSource to update data. They are divided into three types according to their use.

  1. PagaKeyedDataSource<Key, Value>: Applies to scenarios where the target data is sorted by page number.keyIs the number of pages, the data parameter of the request is reincludednext/previousPage number information.
  2. ItemKeyedDataSource<Key, Value>: Applies to the loading of target data depending on the information of a particular Item,keyContains information from Item. For example, the NTH +1 information needs to be loaded based on the NTH Item information. Often used to comment on requests for information.
  3. PositionalDataSource<T>: applies to a fixed target total, loading data through a specific location, key is location information.

Add a dependency to a paging library:

implementation "Androidx. The paging: the paging - runtime: 2.1.1"
implementation "Androidx. The paging: the paging - runtime - KTX: 2.1.1"
implementation 'androidx. Lifecycle: lifecycle - livedata - KTX: 2.2.0'
Copy the code

2. Paging + Room

First of all, this part of the tutorial assumes that you can use Room and ViewModel proficiently, so there will be no detailed explanation of this part of the tutorial.

2.1 Creating a Database

2.1.1 the Entity
@Entity(tableName = "users_table")
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Int? = null.@ColumnInfo(name = "first_name")
    val firstName: String,

    @ColumnInfo(name = "last_name")
    val lastName: String,

    @ColumnInfo(name = "birthday")
    val birthday: String,

    @ColumnInfo(name = "nationality")
    val nationality: String
)
Copy the code
2.1.2 Dao

One thing to note here is that the return value is not List

but DataSource. Where key is on the left and value is on the right. Key is the internal PositionalDataSource to indicate the location of the current data, i.e. the number of pages. Value obviously shows the data we need to use.

@Dao
interface UserDao {
    @Query("SELECT * FROM users_table ORDER BY id ASC")
    fun getAllByLivePage(a): DataSource.Factory<Int, User>
}
Copy the code
2.1.3 the Database
@Database(version = 1, entities = [User::class])
abstract class UserDataBase : RoomDatabase() {

    companion object {
        private var INSTANCE: UserDataBase? = null

        fun getInstance(context: Context): UserDataBase? {
            if (INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(
                    context,
                    UserDataBase::class.java.DATABASE_NAME
                ).build()
            }
            return INSTANCE
        }

        fun destroyInstance(a) {
            INSTANCE = null
        }
        const val DATABASE_NAME = "user_database.db"
    }
    abstract fun getUserDao(a): UserDao
}
Copy the code

2.2 make PagedListAdapter

Instead of inheriting RecyclerAdapter or ListAdapter, we need to inherit PagedListAdapter. The rewritten content is exactly the same as the ListAdapter, nothing special, as follows.

class UserAdapter : PagedListAdapter<User, UserAdapter.UserViewHolder>(diffCallback) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding: ItemUserBinding =
            DataBindingUtil.inflate(inflater, R.layout.item_user, parent, false)

        return UserViewHolder(binding)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int){ getItem(position)? .also { holder.binding.txtBirthday.text = it.birthday holder.binding.txtFirstName.text = it.firstName holder.binding.txtLastName.text = it.lastName holder.binding.txtNationality.text = it.nationality } }class UserViewHolder(var binding: ItemUserBinding) : RecyclerView.ViewHolder(binding.root)

    companion object {
        private val diffCallback = object : DiffUtil.ItemCallback<User>() {
            override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem === newItem
            }
        }
    }
}
Copy the code

2.3 Making the Config for Paging

We need to set the Config for Paging in the ViewModel. I have tried setting it in the Activity, but it did not work properly. I wonder if there is anything wrong with my writing method.

class ShowViewModel(application: Application) : AndroidViewModel(application) {
    valdao = UserDataBase.getInstance( application.applicationContext )? .getUserDao()valallUsers = dao!! .getAllByLivePage() .toLiveData(Config(pageSize =10, enablePlaceholders = true, maxSize = 50))}Copy the code

The data from DB needs to be converted into the familiar LiveData, and the Config needs to be set at the same time.

  1. pageSize: The amount of data loaded from the database at one time.
  2. enablePlaceholders: Indicates whether to display if the load fails. The default istrue.
  3. maxSize: Indicates the maximum amount of data stored in memory. The maximum value of Int by default.
  4. prefetchDistance: Obtain data in advance when the distance to the last data is how far. The default ispageSize.
  5. initialLoadSizeHint: loaded into thepagedListInitial amount of data in. The default ispageSizeThree times as much. It’s usually about a normal page of data.

2.4 Monitor and incoming Adapter

The final step is to monitor the Activity and pass the data into the Adapter for updating as it changes.

viewModel.allUsers.observe(this, Observer {
    adapter.submitList(it)
})
Copy the code

2.5 making

RoomAndPageListDemo

3. Paging + Network

After Paging + Room, we continue to try the combination of Paging + Network. First of all, I didn’t find a good Api, so I’m doing a local simulation here.

And the PagedListAdapter is exactly the same as above, so I won’t repeat it.

3.1 make a DataSource

We need to inherit PageKeyedDataSource

and override the three methods.
,value>

Let’s first look at the data passed in the override function.

  1. params: LoadInitialParams<Int>: This parameter is the initial loading parameter. Contains two variables,requestedLoadSize: The amount of data required.placeholdersEnabledIt’s the same thing as above.
  2. callback: LoadInitialCallback<Key, Value>: is a callback after the initial loading is complete.
  3. params: LoadParams<Int>: is the argument passed in when paging forward and backward. Contains two variables,requestedLoadSizeIt’s the same as up here,keyIs the current page number.
  4. callback: LoadCallback<Key, Value>): is the callback after the forward and backward page is finished.

Here are the parameters to override.

  1. loadInitial: indicates the initial loading.callbackThe function is to remindPagingData has been loaded.onResultThe first argument to is the loaded data. The second argument is the key of the previous page, which can be set to if the data loaded does not have previous datanull. The third parameter is the key on the next page, and I’m starting with 0 here, so I’m passing a 1 here.
  2. loadBefore: is the load when the page is turned forward.onResultThe first parameter is also the requested data, and the second parameter is the number of pages loaded forwardparams.key-1.
  3. loadAfter: is the load when paging backwards.onResultThe first parameter is also the data, and the second parameter is the number of pages when the page is loaded, which is set hereparams.key+1
class UsersDataSource : PageKeyedDataSource<Int, User>() {
    // Start loading
    override fun loadInitial(
        params: LoadInitialParams<Int>,
        callback: LoadInitialCallback<Int, User>
    ) {

        callback.onResult(getList(0, params.requestedLoadSize), null.1)}// forward load
    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, User>) {
        callback.onResult(getList(params.key, params.requestedLoadSize), params.key - 1)}// load backwards
    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, User>) {
        callback.onResult(getList(params.key, params.requestedLoadSize), params.key + 1)}}Copy the code

3.2 make DataSourceFactory

We need to use DataSource.Factory to generate the UsersDataSource we just made. We need to inherit the datasourec. Factory

that appears in Paging+Room above.
,>

class CustomPageDataSourceFactory() : DataSource.Factory<Int, User>() {
    override fun create(a): DataSource<Int, User> {
        return UsersDataSource()
    }
}
Copy the code

3.3 Generate LiveData through LivePagedListBuilder

Finally, we will set Paging in the Activity. Two parameters need to be passed in the LivePagedListBuilder. Factory

The second one is pagedlist. Config, which is skipped because it’s described above.
,value>

// Generate LiveData from LivePagedListBuilder
val data = LivePagedListBuilder(
        CustomPageDataSourceFactory(),
        PagedList.Config.Builder()
        .setPageSize(20)
        .setInitialLoadSizeHint(60)
        .build()
    ).build()

// Monitor data and pass it to adapter when changes occur
data.observe(this, Observer {
    adapter.submitList(it)
})
Copy the code

3.4 making

PagingDemo

4. Conclusion

The Paging library enables seamless data loading and better user experience. In particular, there is better support for Room, which can reduce the amount of development.