preface

A few days ago, Google updated several new members of Jetpack, Hilt, Paging 3, App Startup, etc. In the previous article, we divided what App Startup is and what problems App Startup solves for us. If you haven’t seen it before, click on the link below to view the article and code.

  • AndroidX App Startup practice and principle analysis of Jetpack’s latest member
  • AppStartup code address: https://github.com/hi-dhl/AndroidX-Jetpack-Practice

Today, this article is mainly to analyze Paging3, Paging3 will be divided into three articles, detailed analysis of its principle, each article has a complete project example.

  • Jetpack member Paging3 Database practice and source code Analysis (1)
  • Jetpack Member Paging3 Network Practice and Principle Analysis (II)
  • Jetpack member Paging3 uses RemoteMediator to load network page data and update it to the database.

In this article you will learn the following:

  • What is Paging3?
  • Paging3 in the project structure and the function of the class source code analysis?
  • How do I use Paging3 correctly in my project?
  • What is a Data Mapper?
  • What is Kotlin Flow?

Before analyzing, let’s take a look at the techniques used in this article’s actual combat project:

  • [2.4K Star] Drop Dagger to embrace Koin.

  • In my previous post bye-bye buildSrc, embracing My Composing builds to improve Android build speed, using my buildbuilds as a dependency on my repository for versioning.

  • JDataBinding is my library based on DataBinding encapsulation, as you can see from my previous article: Encapsulation Kotlin + Android DataBinding in the project.

  • Data Mapper: Transforming the entity of Data source into the model used by the upper layer plays a very important role in the project. I have read many projects, but this concept is rarely mentioned. When I read the articles written by foreign masters, they mentioned this concept, which will be analyzed in detail later.

  • Some of the Kotlin techniques were used in the project. Check out my other article: Kotlin techniques and principles that few people know about.

  • There are also Paging 3, Room, Anko, Repository design pattern, MVVM architecture, and so on.

What is Paging3?

Paging is a Paging library that helps you load display data from local storage or over a network. This allows your App to use network bandwidth and system resources more efficiently.

Paging3 is a completely rewritten library using Kotlin coroutines. After going from Paging1x to Paging2x to now Paging3, I realized that Paging3 is really much more convenient than Paging1 and Paging2.

Google recommends using Paging as part of App architecture, which can be easily integrated with Jetpack components. Paging3 contains the following functions:

  • Caching paging data in memory ensures that your App uses system resources efficiently when using paging data.
  • Built-in deduplication requests ensure that your App uses network bandwidth and system resources efficiently.
  • RecyclerView adapters can be configured to automatically request data when the user scrolls to the end of the load data.
  • Support for Kotlin coroutines and flows, as well as LiveData and RxJava.
  • Built-in error handling support, including refresh and retry features.

Paging3 structure and class function source code analysis

Google recommends that we operate in three layers of the application when using Paging3, and how they work together to load and display paging data, as shown below:

However, I personally think that a layer of Data Mapper should be added (detailed below), as shown in the figure below:

Data Mapper transforms the Data source entity into the model used by the upper layer, which is often ignored by us, but it plays a very important role in the project. I have read many projects, but this concept is rarely mentioned. I only mentioned this concept in the articles written by foreign big oxen. A separate article will be written about Data Mapper later, which will be verified by Demo. Here is just a brief mention.

Data Mapper

In a fast-developing project, in order to complete the delivery of the first version as soon as possible, the data source and UI are subconsciously bound together. When the business gradually increases, the data source changes, and the upper layer also needs to change with it, resulting in a lot of reconstruction work in the later stage, because the core reason is too strong coupling.

The advantages of using Data Mapper are as follows:

  • Changes to the data source do not affect upper-level business.
  • A bad back-end implementation doesn’t affect the business at the top (imagine if you were forced to perform two network requests because the back-end couldn’t provide all the information you needed in one request, would you let that affect your entire code).
  • Data Mapper facilitates unit testing to ensure that upper-layer services will not be affected by changes in Data sources.
  • In this article, the case project Paging3Simple uses Data Mapper as a Data map, which is commented in detail in the code.

Repository layer

The PagingSource object in the Paging3 component is used in the Repository layer. Each PagingSource object defines a data source and how to find data from that data source. The PagingSource object can load data from any data source, both network and local.

PagingSource is an abstract class with two important methods: Load and getRefreshKey. The load method looks like this:

abstract suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value>
Copy the code

This is a suspend function that implements this method to trigger asynchronous loading, and another getRefreshKey method

open fun getRefreshKey(state: PagingState<Key, Value>): Key? = null
Copy the code

This method is called only if the initial load succeeds and the list of loaded pages is not empty.

Also in this layer is RemoteMediator, another component of Paging3. The RemoteMediator object handles paging from hierarchical data sources, such as network data sources with a local database cache.

ViewModel layer

At the ViewModel layer, we use the Paging3 component Pager, which is the main entry page, PagingConfig, initialKey, remoteMediator, pagingSourceFactory are accepted in its constructor as follows:

class Pager<Key : Any, Value : Any>
@JvmOverloads constructor(
    config: PagingConfig,
    initialKey: Key? = null,
    @OptIn(ExperimentalPagingApi::class)
    remoteMediator: RemoteMediator<Key, Value>? = null,
    pagingSourceFactory: () -> PagingSource<Key, Value>
)
Copy the code

Today’s article and project mainly use PagingConfig and PagingSource. PagingSource has been mentioned above, so we mainly divide into PagingConfig.

Val pagingConfig = pagingConfig (// How much data is displayed on each page pageSize = 60, // Turn on the placeholder enableplaceholder = true, PrefetchDistance = 3, /** * Default: pageSize * 3 * * internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3 * val initialLoadSize Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER */ initialLoadSize = 60, Slide load more data */ maxSize = 200)Copy the code

Connecting the ViewModel layer to the UI layer uses the Paging3 component PagingData, which is a container for PagingData that queries a PagingSource object and stores the results.

Google recommends putting the component Pager in the ViewModel Layer, but I prefer to put it in the Repository layer, as described below.

UI layer

In the UI layer, the main component of Paging3, PagingDataAdapter, PagingDataAdapter is a recyclable view adapter that handles paging data, You can use the AsyncPagingDataDiffer component to build your own custom adapters, the PagingDataAdapter used in this article.

How is Paging 3 used in a project

Add the following code to the build.gradle file in your App module:

Dependencies {def paging_version = "3.0.0-alpha01" implementation "androidx.paging: Paging - Runtime :$paging_version"}Copy the code

Next, I will implement each layer as mentioned above. First, let’s take a look at the structure of the project.

  • Bean: store the upper layer required model, and RecyclerView Adapter bound together.
  • Loca: stores operations related to the local database.
  • Mapper: Data mapping, which converts data source entities into upper-level models.
  • Repository: Mainly handles data source-related operations (local, network, in-memory cache, etc.).
  • Di: Relates to dependency injection.
  • UI: Presentation of data.

Database section

@Dao
interface PersonDao {

    @Query("SELECT * FROM PersonEntity order by updateTime desc")
    fun queryAllData(): PagingSource<Int, PersonEntity>

    @Insert
    fun insert(personEntity: List<PersonEntity>)

    @Delete
    fun delete(personEntity: PersonEntity)
}
Copy the code

To explain the Dao, the queryAllData method returns a PagingSource, which is later converted to flow > by Pager.

The Repository part

RepositoryFactory (RepositoryFactory) is RepositoryFactory (Koin, RepositoryFactory).

Class RepositoryFactory(Val appDataBase: appDataBase) {// Reposit PagingConfig and Data Mapper Fun makeLocalRepository(): Repository = PersonRepositoryImpl(appDataBase, pagingConfig,Person2PersonEntityMapper(), PersonEntity2PersonMapper ()) val pagingConfig = pagingConfig (/ / the size of the display of data pageSize = 60 per page, So, when enableplaceholder = true, // How far away is it when a product is loaded, prefetchDistance = 3, Default: pageSize * 3 * * internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3 * val initialLoadSize Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER */ initialLoadSize = 60, Slide load more data */ maxSize = 200)}Copy the code

Here, PagingConfig and Data Mapper are generated and passed to the PersonRepositoryImpl. Let’s take a look at the PersonRepositoryImpl code.

class PersonRepositoryImpl( val db: AppDataBase, val pageConfig: PagingConfig, val mapper2PersonEntity: Mapper<Person, PersonEntity>, val mapper2Person: Mapper<PersonEntity, Person> ) : Repository { private val mPersonDao by lazy { db.personDao() } override fun postOfData(): Flow<PagingData<Person>> {return Pager(pageConfig) {// Load database data mPersonDao.queryallData ()}.flow.map {PagingData -> Person pagingdata.map {mapper2person.map (it)}}}}Copy the code

Pager is the main entry page and accepts PagingConfig, pagingSourceFactory in its constructor.

pagingSourceFactory: () -> PagingSource<Key, Value>
Copy the code

PagingSourceFactory is a lambda expression that can be represented directly in curly braces in Kotlin, where a request to load data from the database is executed.

Finally, we call flow to return flow >, and then transform the database entity PersonEntity into the entity Person used by the upper layer through flow’s map.

Flow library is a new library added after the release of Kotlin Coroutines 1.3.2. It is also called asynchronous Flow, which is similar to RxJava Observable. In this paper, map method in Flow is mainly used for data conversion, as shown in the following simple examples:

flow{ for (i in 1.. 4) { emit(i) } }.map { it * it }Copy the code

Pagingdata.map {mapper2Person.map(it)} this line of code, where mapper2Person is our own implementation of Data Mapper, code as follows:

class PersonEntity2PersonMapper : Mapper<PersonEntity, Person> {
    override fun map(input: PersonEntity): Person = Person(input.id, input.name, input.updateTime)
}
Copy the code

The database entity PersonEntity is converted to the entity Person used by the upper layer.

The UI parts

MainViewModel is injected via the KOIN dependency and the Repository parameter is passed.

class MainViewModel(val repository: Repository) : ViewModel() {// Call Flow's asLiveData method to LiveData val pageDataLiveData3: LiveData<PagingData<Person>> = repository.postOfData().asLiveData() }Copy the code

Register observe in the Activity and bind the data to the Adapter as follows:

mMainViewModel.pageDataLiveData3.observe(this, Observer { data ->
    mAdapter.submitData(lifecycle, data)
})
Copy the code

Knowledge expansion

We just called the asLiveData method to convert it to LiveData, but there are two other methods.

Methods a

The method used prior to LifeCycle 2.2.0 uses two LiveData, one mutable and one immutable, as follows:

// Private MutableLiveData mutable, internal access to private val _pageDataLiveData: MutableLiveData<Flow<PagingData<Person>>> by lazy {MutableLiveData<Flow<PagingData<Person>>>()} Query only val pageDataLiveData: LiveData<Flow<PagingData<Person>>> = _pageDataLiveData _pageDataLiveData.postValue(repository.postOfData())Copy the code
  • Prepare a private MutableLiveData for internal access only.
  • Expose immutable LiveData externally.
  • Assign the value to _pageDataLiveData.

Method 2

LifeCycle 2.2.0 can be done in a more streamlined way using the LiveData Coroutine Builder.

val pageDataLiveData2 = liveData {
    emit(repository.postOfData())
}
Copy the code

The liveData coroutine constructor provides a block of coroutine code that produces an immutable liveData, and the emit() method updates the liveData.

Finally add the left and right swipe delete function

Call RecyclerView encapsulated ItemTouchHelper to slide left and right to delete item function.

Private fun initSwipeToDelete () {/ * * * is located in the [androidx. Recyclerview. Widget] package, has good control * / ItemTouchHelper (object: ItemTouchHelper.Callback() { override fun getMovementFlags( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder ): Int = makeMovementFlags(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean = false override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { (viewHolder as PersonViewHolder).mBinding.person? .let {mmainViewModel.remove (it)}}).attachToRecyclerView(rvList)}Copy the code

The topic of Paging loading local data ends here, we will explain how to load network data in the next article, and finally the last effect diagram.

conclusion

This article mainly introduces the following contents:

What is Paging3 and its advantages

Paging is a Paging library that helps you load and display data from local storage or over a network. This approach makes your App use network bandwidth and system resources more efficiently, whereas Paging3 is a completely rewritten library using Kotlin coroutines:

  • Caching paging data in memory ensures that your App uses system resources efficiently when using paging data.
  • Built-in deduplication requests ensure that your App uses network bandwidth and system resources efficiently.
  • RecyclerView adapters can be configured to automatically request data when the user scrolls to the end of the load data.
  • Support for Kotlin coroutines and flows, as well as LiveData and RxJava.
  • Built-in error handling support, including refresh and retry capabilities.

Paging3 structure and class function source code analysis

  • PagingSource: Each PagingSource object defines a data source and how to find data from that source.
  • RemoteMediator: The RemoteMediator object handles paging from hierarchical data sources, such as network data sources with a local database cache.
  • Pager: is the main entry page that accepts PagingConfig, initialKey, remoteMediator, pagingSourceFactory in its constructor.
  • PagingDataAdapter: A recyclable view adapter that handles paging data, you can use the AsyncPagingDataDiffer component to build your own custom adapter.

Data Mapper

Data Mapper transforms the Data source entity into the model used by the upper layer, which is often ignored by us, but plays a very important role in the project. The advantages of using Data Mapper are as follows:

  • Changes to the data source do not affect upper-level business.
  • A bad back-end implementation doesn’t affect the business at the top (imagine if you were forced to perform two network requests because the back-end couldn’t provide all the information you needed in one request, would you let that affect your entire code).
  • Data Mapper facilitates unit testing to ensure that upper-layer services will not be affected by changes in Data sources.
  • The Data Mapper will be used as a Data map in this article’s case project Paging3Simple.

Kotlin Flow

Flow library is a new library added after the release of Kotlin Coroutines 1.3.2. It is also called asynchronous Flow, which is similar to RxJava Observable. This paper mainly uses map method in Flow to transform data, as shown in the following examples:

flow{ for (i in 1.. 4) { emit(i) } }.map { it * it }Copy the code

Map {mapper2person.map (it)} = pagingdata.map {mapper2person.map (it)}

GitHub address: https://github.com/hi-dhl/AndroidX-Jetpack-Practice

Is building a most complete and latest AndroidX Jetpack related components of the actual combat project and related components of the principle of analysis article, currently has included App Startup, Paging3, Hilt and so on, is gradually adding other Jetpack new members, the warehouse continues to update, Check it out: AndroidX-Jetpack-Practice. If this warehouse is helpful, please give me a like on the top right corner of the warehouse.

conclusion

Dedicated to sharing a series of Android system source code, reverse analysis, algorithm, translation, Jetpack source code related articles, you can follow me, if this article is helpful to you give a star, is trying to write better articles, to learn together, looking forward to growing with you.

algorithm

Since LeetCode has a large question bank, hundreds of questions can be selected for each category. Due to the limited energy of each person, it is impossible to brush all the questions. Therefore, I sorted the questions according to the classic types and the difficulty of the questions.

  • Data structures: arrays, stacks, queues, strings, linked lists, trees…
  • Algorithms: Search algorithm, search algorithm, bit operation, sorting, mathematics,…

Each problem will be implemented in Java and Kotlin, and each problem has a solution idea. If you like algorithms and LeetCode like me, you can pay attention to my Solution of LeetCode problem on GitHub: Leetcode-Solutions-with-Java-And-Kotlin, come to learn together And look forward to growing with you.

Android 10 source code series

I’m writing a series of Android 10 source code analysis articles. Knowing the system source code is not only helpful in analyzing problems, but also very helpful in the interview process. If you like to study Android source code as MUCH as I do, You can follow my Android10-source-Analysis on GitHub, and all articles will be synchronized to this repository.

  • How is APK generated
  • APK installation process
  • 0xA03 Android 10 source code analysis: APK loading process of resource loading
  • Android 10 source code: APK
  • Dialog loading and drawing process and use in Kotlin, DataBinding
  • WindowManager View binding and architecture
  • 0xA07 Android 10 source code analysis: Window type and 3d view hierarchy analysis
  • More……

Android Apps

  • How to get video screenshots efficiently
  • How to package Kotlin + Android Databinding in your project
  • Bye-bye buildSrc, embrace Composing builds for faster Android builds
  • Few people know Kotlin’s technique and principle analysis
  • AndroidX App Startup practice and principle analysis of Jetpack’s latest member

Select a translation

At present, I am sorting out and translating a series of selected foreign technical articles. Besides translation, many excellent English technical articles provide good ideas and methods. Every article has a part of the translator’s thinking and a deeper interpretation of the original text. You can pay attention to my Technical-Article-Translation on GitHub, and articles will be synchronized to this warehouse.

  • [Google engineers] just released a new Fragment feature, “New ways to transfer Data between Fragments” and source code analysis
  • How does FragmentFactory elegantly use Koin and partial source code analysis
  • [2.4K Start] Drop Dagger to Koin
  • [5K +] Kotlin’s performance optimization stuff
  • Decrypt RxJava’s exception handling mechanism
  • [1.4K+ Star] Picasso
  • More……

Tool series

  • Shortcuts to AndroidStudio that few people know
  • Shortcuts to AndroidStudio that few people know
  • All you need to know about ADB commands
  • 10 minutes introduction to Shell scripting

The reverse series

  • Dynamically debug APP based on Smali file Android Studio
  • The Android Device Monitor tool cannot be found in Android Studio 3.2