introduce

Paging is mainly used in combination with RecyclerView, which is a Paging loading solution. In this way, Paging only loads a part of the total data at a time. Room is an ORM library provided by Google. The code for this article comes from the official example: the official example address

Using the Paging Room

  1. Add the dependent

    def room_version = "2.2.0 - alpha02"
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-rxjava2:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
    kapt "androidx.room:room-compiler:$room_version"

    def paging = "2.1.0."
    implementation "androidx.paging:paging-runtime-ktx:$paging"


Copy the code
  1. The example of database creation is to obtain data source through Room database, which is used to display our data in Recyclerview. However, the normal development is mainly to obtain data source through network request (network request and GitHub code of Paging).

A brief introduction to Room. Room provides three main components:

@DATABASE: @Database is used to annotate classes, and the annotated class must be an abstract class inherited from RoomDatabase. This class is used to create databases and daOs. An implementation class of XXX (class name) _Impl is generated

@Entity: @Entity is used to annotate an Entity class. @Database references the class annotated by @Entity through its entities property and creates a table using all of the fields of that class as column names for the table. Classes annotated with @Database must specify a parameterless method that returns the class annotated with @DAO

@DAO: @DAO is used to annotate an interface or abstract method that provides access to a database. (1) Create the Student entity class, which defines the primary key increment

@Entity
data class Student(@PrimaryKey(autoGenerate = true) val id: Int,val name: String)
Copy the code

(2) Create Dao: define some database operation methods. DataSource represents the consciousness of the DataSource, and the change of the DataSource will drive the update of UI.

@Dao
interface StudentDao {

    @Query("Select * from Student ORDER BY name COLLATE NOCASE ASC ") fun queryByName(): DataSource.Factory<Int,Student> @insert List<Student>) @Insert fun insert(student: Student) @Delete fun delete(student: Student) }Copy the code

(3) create database:

@Database(entities = arrayOf(Student::class) ,version = 1)
abstract class StudentDb : RoomDatabase(){
    abstract fun studentDao(): StudentDao

    companion object{
        private var instance: StudentDb? = null

        @Synchronized
        fun get(context: Context): StudentDb{
            if(instance == null){
                instance = Room.databaseBuilder(context.applicationContext,
                    StudentDb::class.java,"StudentDataBase")
                    .addCallback(object : Callback() {
                        override fun onCreate(db: SupportSQLiteDatabase) {
                            fillInDb(context.applicationContext)
                        }
                    }).build()
            }
            returninstance!! } private fun fillInDb(context: Context){ ioThread{ get(context).studentDao().insert(STUDENT_DATA.map { Student(id = 0,name = it) }) } } } } private val  STUDENT_DATA = arrayListOf(.........) ;Copy the code
  1. The UI displays (1) StudentViewHolder is created
class StudentViewHolder (parent: ViewGroup) : RecyclerView.ViewHolder(
    LayoutInflater.from(parent.context).inflate(R.layout.adapter_paging,parent,false)){
    private val nameView = itemView.findViewById<TextView>(R.id.name)

    var student : Student? = null

    fun bindTo(student: Student?) { this.student = student nameView.text = student? .name } }Copy the code

(2) Create the PagedListAdapter implementation class. The diffUtil.itemCallback <> instance calls back to the two abstract methods in DiffUtil.itemCallback when the data source changes to confirm whether the data and previous changes have occurred, and if so, calls Adapter to update the UI. AreContentsTheSame method: Whether the data content has changed

class StudentAdapter : PagedListAdapter<Student,StudentViewHolder>(diffCallback){
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudentViewHolder {
        return StudentViewHolder(parent)
    }

    override fun onBindViewHolder(holder: StudentViewHolder, position: Int) {
        holder.bindTo(getItem(position))
    }


    companion object{
        private val diffCallback = object : DiffUtil.ItemCallback<Student>(){
            override fun areContentsTheSame(oldItem: Student, newItem: Student): Boolean {
                Log.e("tag"."areContentsTheSame"+Thread.currentThread().name+ oldItem.name +" new :"+newItem.name)
                return oldItem.id == newItem.id
            }

            override fun areItemsTheSame(oldItem: Student, newItem: Student): Boolean {
                Log.e("tag"."areItemsTheSame"+ oldItem.name +" new :"+newItem.name)
                return oldItem == newItem
            }
        }
    }
}
Copy the code

(3) Create ViewModel toLiveData method internally through LivePagedListBuilder to build PagedList. It returns LiveData<PagedList>, which is used by the UI layer to listen for data source changes.

Config is the class used to build configuration for PagedList. PageSize is used to specify the amount of data per page. Enableplaceholder indicates whether unloaded data is stored in a PageList with null.

class StudentViewModel (app: Application) : AndroidViewModel(app){
    val dao = StudentDb.get(app).studentDao()

    val allStudents = dao.queryByName().toLiveData(Config(pageSize = 30,enablePlaceholders = true,maxSize = Int.MAX_VALUE))

    fun insert(name: String) = ioThread{
        dao.insert(Student(id = 0,name = name))
    }

    fun remove(student: Student) = ioThread{
        dao.delete(student)
    }
}
Copy the code

(4) Add an observer to LiveData<PagedList> in the ViewModel in the Activity. Whenever changes are observed in the data source, You can call the StudentAdapter submitList method to present the latest data to the Adapter.

class PagingActivity : AppCompatActivity(){

    private lateinit var viewModel: StudentViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_paging)
        viewModel = ViewModelProviders.of(this).get(StudentViewModel::class.java)
        val adapter = StudentAdapter()
        studenrecy.adapter = adapter
        viewModel.allStudents.observe(this, Observer(adapter::submitList))

        initAddButtonListener()

        initSwipeToDelete()
    }

    private fun initSwipeToDelete(){
        ItemTouchHelper(object : ItemTouchHelper.Callback(){
            override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
                return makeMovementFlags(0,ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT)
            }

            override fun onMove(
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder,
                target: RecyclerView.ViewHolder
            ): Boolean {
                return false} override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { (viewHolder as StudentViewHolder).student? .let { viewModel.remove(it) } } }).attachToRecyclerView(studenrecy) } private funaddCheese() {
        val newCheese = inputText.text.trim()
        if (newCheese.isNotEmpty()) {
            viewModel.insert(newCheese.toString())
            inputText.setText("")
        }
    }
    private fun initAddButtonListener(){
        addButton.setOnClickListener {
            addCheese()
        }
        // when the user taps the "Done" button in the on screen keyboard, save the item.
        inputText.setOnEditorActionListener { _, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                addCheese()
                return@setOnEditorActionListener true
            }
            false // action that isn't DONE occurred - ignore } // When the user clicks on the button, or presses enter, save the item. inputText.setOnKeyListener { _, keyCode, event -> if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) { addCheese() return@setOnKeyListener true } false // event that isn't DOWN or ENTER occurred - ignore
        }
    }
}
Copy the code

summary

To summarize the general process: (1) when a new data is inserted into the database as the data source changes, the callback to the DataSource. InvalidatedCallback, The DataSource is initialized in the compute method of computable Data (datasourceFactory.create ()). (2) The LiveData background thread creates a new PagedList. The new PagedList will be sent by mliveData.postValue (value) to the PagedListAdapter of the UI thread. (3) The PagedListAdapter uses DiffUtil to compare the difference between the current Item and the new Item. When the end of contrast, PagedListAdapter by calling RecycleView. Adapter. NotifyItemInserted () insert a new item to the appropriate location.

The specific process is shown in the figure below:

PositionalDataSource: Mainly used to load data countable finite data. Such as loading a local database, corresponding WrapperPositionalDataSource repackaging class. There is also a subclass LimitOffsetDataSource, which is returned by the database.

ItemKeyedDataSource: Mainly used to load increments of data. Such as network request data with continuous data from the request of more and more, corresponding WrapperItemKeyedDataSource wrapper class.

PageKeyedDataSource: This is similar to ItemKeyedDataSource in that it’s for data that keeps increasing. Here is paging network request for data, corresponding WrapperPageKeyedDataSource wrapper class.

(2) PageList: The core class. It takes data from the data source, controls how much data is loaded by default the first time, how much data is loaded each time, and so on, and reflects the changes of data to the UI. (4) DataSource.Factory this interface implementation class is mainly used to obtain the DataSource DataSource. (5) PagedListAdapter inherit from RecyclerView.Adapter, RecyclerView Adapter, through DiffUtil analysis of data is changed, responsible for processing UI display logic. (6) LivePagedListBuilder generates the corresponding PagedList through this class, and there are mainly computable table Data classes inside. The main processes and classes are as follows:

Official website Address