preface

In the last article, I focused on the story of Jetpack-Paging2. Because Paging3 changes a lot, and in order to let more people adapt to the two versions at the same time, so this article will combine Flow and Paging3 to explain the combination.

Because this article mainly around Paging3 and the difference with Paging2 to explain!

Therefore, this article needs knowledge points:

  • ViewBinding+DataBinding+Flow+ViewModel+KT+ coroutine
  • Familiar with Paging2, better reading experience (try not to have a look)

Review Jetpack — Paging2

Review the previous article on Paging2!

Were there three core categories mentioned?

So! What about the core class of Paging3? What are they?

  • Paging3 Core class PagingDataAdapter (original PagedListAdpater)
  • Paging3 Core class PagingData (original PagedList)
  • Paging3 Core class PagingSource (original DataSource in three formats)

So the change between them is just a nomenclature change?

NO! NO! NO! If that were true, there would not be a separate explanation!

2. Jetpack — Paging3

2.1 Configuration Preparations

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'}... CompileOptions {sourceCompatibility JavUncomfortable.VERSION_1_8 targetCompatibility javUncomfortable.VERSION_1_8} kotlinOptions { jvmTarget ='1.8'
    }
    buildFeatures {
        viewBinding = true 
    }
    dataBinding {
        enabled = true}... Slightly implementation'org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.4.2'
    implementation "Androidx. Activity: activity - KTX: 1.1.0." "
    implementation "Androidx. Fragments: fragments - KTX: 1.2.5." "
    implementation "Androidx. Lifecycle: lifecycle - livedata - KTX: 2.2.0." "
    implementation "Com. Squareup. Retrofit2: retrofit: 2.9.0"
    implementation "Com. Squareup. Retrofit2: converter - gson: 2.9.0"
    implementation 'com. Squareup. Okhttp3: logging - interceptor: 3.4.1 track'
    implementation 'androidx. The paging: the paging - runtime: 3.0.0 - alpha03'
    implementation 'com. Squareup. Picasso was: Picasso was: 2.71828'
    implementation 'androidx. Swiperefreshlayout: swiperefreshlayout: 1.0.0'
Copy the code

As you can see here, I have both ViewBinding and DataBinding enabled, and then introduced the Kotlin-Kapt plugin and a number of libraries.

Network permissions as well as allowing Http are essential

    <uses-permission android:name="android.permission.INTERNET" />

    <application
		.slightlyandroid:networkSecurityConfig="@xml/network_security_config">
        
        <activity android:name=".activity.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
Copy the code

There’s nothing to be said for that

network_security_config.xml


      
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
Copy the code

Here is ready to work, now still in accordance with the above order to explain Paging3!

2.2 PagingDataAdapter

class MovieAdapter(private val context: Context) :
    PagingDataAdapter<Movie, BindingViewHolder>(object : DiffUtil.ItemCallback<Movie>() {
        override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
            // kotlin === reference, == content
            // Java == reference, equals content
            return oldItem == newItem
        }

    }) {
    override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
        valmovie = getItem(position) movie? .let {// The DataBinding logic is not detailed here
            val binding = holder.binding as MovieItemBinding
            binding.movie = it
            binding.networkImage = it.cover
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
        val binding = MovieItemBinding.inflate(LayoutInflater.from(context), parent, false)
        return BindingViewHolder(binding)
    }
    
}
Copy the code

As you can see, the corresponding Adapter is the same as before, except that Item is changed to DataBinding, and the corresponding difference comparison needs to be implemented. And the logic is similar to what we got before.

However, the corresponding BindingViewHolder has a single class (not a class component) for convenience.

class BindingViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root)
Copy the code

Combined with ViewBinding, the code is just a bare sentence.

And, of course, the Json entity class

data class Movies(
    @SerializedName("subjects")
    val movieList: List<Movie>,
    @SerializedName("has_more")
    val hasMore: Boolean
)
Copy the code

Here’s the 4.2 return structure from the previous article!

conclusion

This core class feels similar to the previous use of Paging2.

Let’s see the next one!

2.3 PagingData

class MovieViewModel : ViewModel() {

    private val movies by lazy {
        Pager(
            config = PagingConfig(pageSize = 8, prefetchDistance = 8, initialLoadSize = 16),
            pagingSourceFactory = { MoviePagingSource() })
            .flow   // Convert to Flow
            .cachedIn(viewModelScope) // Bind its lifecycle to the ViewModel
    }

    fun loadMovie(a): Flow<PagingData<Movie>> = movies
}
Copy the code

Code parsing:

  • We can see here that the Pager object is instantiated here by lazy loading lazy

    • pageSize Of course, this is how long each page is
    • prefetchDistance Prefetch distance, which indicates how far from the edge of the loaded content it must be to trigger further loading. Generally, the value is greater than 0 and less than or equal to 0pageSize
    • initialLoadSizeThe initial load from PagingSource defines the load size of the request, which is usually greater than pageSize, so that when the data is first loaded, the content range is large enough to cover the small scroll.
  • The corresponding Page object is converted into a Flow Flow, and its lifecycle is then bound to the ViewModel via cachedIn

  • Finally, the corresponding Flow is returned through the loadMovie method

Here we see that when the Pager object is initialized, pagingSourceFactory = {MoviePagingSource()} specifies the corresponding factory as MoviePagingSource

so

2.4 PagingSource

class MoviePagingSource : PagingSource<Int, Movie>() {

    //currentPage,pageSize
    / / 1 dec
    / / 3, 8
    / / 4, 8

    //prevKey,nextKey
    //null,3
    / / 2, 4
    / / 3, 5

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> {
        delay(2000) // Hang for 2 seconds in order to show more loading effect
        valcurrentPage = params.key ? :1
        val pageSize = params.loadSize
        val movies = RetrofitClient.createApi(MovieApi::class.java).getMovies(currentPage, pageSize)
        Log.d("hqk"."currentPage:$currentPage,pageSize:$pageSize")

        var prevKey: Int? = null
        var nextKey: Int? = null

        val realPageSize = 8
        val initialLoadSize = 16
        if (currentPage == 1) {
            prevKey = null
            nextKey = initialLoadSize / realPageSize + 1
        } else {
            prevKey = currentPage - 1
            nextKey = if (movies.hasMore) currentPage + 1 else null
        }
        Log.d("hqk"."prevKey:$prevKey,nextKey:$nextKey")

        return try {
            LoadResult.Page(
                data = movies.movieList,
                prevKey = prevKey,
                nextKey = nextKey
            )
        } catch (e: Exception) {
            e.printStackTrace()
            return LoadResult.Error(e)
        }
    }
}
Copy the code

Look out, here’s the big deal!

  • Remember the arguments passed above to create the PagingConfig object.

  • The third parameter initialLoadSize = 16 indicates that 16 data will be loaded for the first time.

  • So in this case the first page load, the first page load, is also going to load 16 pieces of data, and the pageSize that we set is equal to 8,

  • That is, when the first page is loaded, the first and second pages of data have been successfully loaded,

  • So when you load the next page, you should start loading from the third page

        //currentPage,pageSize // indicate the currentPage number and the number of pages loaded
        / / 1 dec
        / / 3, 8
        / / 4, 8
    
        //prevKey,nextKey // Current page number,next page number, null indicates the first page loaded
        //null,3
        / / 2, 4
        / / 3, 5
    Copy the code
  • That is, starting with page 3, the number of pages on each subsequent page is the number of pages corresponding to the pageSize setting

Pay attention to the focus again!!

  • I have to do some correspondence hereEven if you don’tinitialLoadSizeProperties,The official default is 3 times larger for the corresponding attributepageSize .
  • If you don’t deal with itprevKey,nextKeyThen you load the second and third pages with data that was loaded the first time!
  • You will see a bug in the list with two pages of duplicate data!

Take a look at the official code

    @JvmField
    @IntRange(from = 1)
     // Multiply by 3 by default
    val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER,
Copy the code

And then,

2.5 How to use it

class MainActivity : AppCompatActivity() {

    private val movieViewModel by viewModels<MovieViewModel>()

    private val mBinding: ActivityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        val movieAdapter = MovieAdapter(this)

        mBinding.apply {
            recyclerView.adapter = movieAdapter.withLoadStateFooter(MovieLoadMoreAdapter(this@MainActivity))
            swipeRefreshLayout.setOnRefreshListener {
                movieAdapter.refresh()
            }
        }

        lifecycleScope.launchWhenCreated {
            movieViewModel.loadMovie().collectLatest {
                movieAdapter.submitData(it)
            }
        }

        lifecycleScope.launchWhenCreated {
            movieAdapter.loadStateFlow.collectLatest { state ->
                mBinding.swipeRefreshLayout.isRefreshing = state.refresh is LoadState.Loading
            }
        }
    }
}
Copy the code

Pay attention to

  • Used heremovieAdapter.withLoadStateFooter(MovieLoadMoreAdapter(this@MainActivity)).
  • The loading tail (load more) corresponding to the corresponding Adapter is set toMovieLoadMoreAdapter

Finally, let’s see how it works

Ha ha ha ha, enough specific bar, so do not understand to hit me. Coordinates chengdu!!

Demo address: click me to download

conclusion

That’s it for jetpack-Paging3. The next article will combine Jetpack’s previous knowledge into a practical application!