Before I wrote a paging2. x is the use and analysis, the effect of paging2. x running infinite sliding is quite good, but the code is a bit troublesome to write, the function is not too perfect, for example, the method of pull down refresh is not provided, We’ll have to call the DataSource#invalidate() method ourselves to reset the data. Google recently released a beta version of 3.0 that is more powerful and easier to use, so let’s get started
Take a look at the features of Paging3.0 on the official website
- Paging data is cached in memory to ensure that applications use system resources more efficiently when processing paging data
- Multiple simultaneous requests trigger only one, ensuring that the App uses network and system resources efficiently
- You can configure RecyclerView adapters to slide to the end to automatically initiate requests
- There is good support for Kotlin coroutines and flows, as well as LiveData and RxJava
- Built-in refresh, retry, error handling and other functions
To get started, the dependency library is introduced first
def paging_version = "3.0.0 - alpha02"
implementation "androidx.paging:paging-runtime:$paging_version"
Copy the code
Configure a RecyclerView, mainly need two parts one is Adapter, one is data. Start with the Adapter
Build the Adapter
Paging2.x extends PagedListAdapter. In 3.0, PagedListAdapter is no longer available, so you need to inherit PagingDataAdapter
class ArticleAdapter : PagingDataAdapter<Article,ArticleViewHolder>(POST_COMPARATOR){
companion object{
val POST_COMPARATOR = object : DiffUtil.ItemCallback<Article>() {
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean =
oldItem == newItem
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean =
oldItem.id == newItem.id
}
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int){ holder.tvName.text = getItem(position)? .title }override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
return ArticleViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item,parent,false))}}class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val tvName: TextView = itemView.findViewById(R.id.tvname)
}
Copy the code
Write the same as normal recyclerView. Adapter, just add one thing, need to pass a diffUtil. ItemCallback in the constructor to determine the calculation rule of the difference update.
Now that the Adapter is finished, here’s the data. We use Retrofit and The Kotlin coroutine to get the data from the network and set it to the Adapter
Get the data and set it to the Adapter
According to the official website, Google recommended that I use a three-tier architecture to complete the setup of data to Adapter, as shown below on the official website
The first data warehouse layer Repository
The Repository layer uses the PagingSource component, each of which corresponds to a data source and how to find data from that source. PagingSource can look up data from any single data source such as the network or database.
The Repository layer also has another paging component that can use RemoteMediator, which is a hierarchical data source, such as a network data source with a local database cache.
Let’s create our PagingSource and Repository
class ArticleDataSource:PagingSource<Int,Article>() {
/** * Implement this method to trigger asynchronous loading (e.g. from a database or network). Suspend is a suspend function that can be conveniently loaded asynchronously using coroutines
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
return try {
valpage = params.key? :0
// Get network data
val result = WanRetrofitClient.service.getHomeList(page)
LoadResult.Page(
// Data to load
data = result.data.datas,
// Set this parameter if more can be loaded, otherwise not
prevKey = null.// Add a key to the next page
nextKey = if(result.data.curPage==result.data.pageCount) null else page+1)}catch (e:Exception){
LoadResult.Error(e)
}
}
}
Copy the code
- Inheriting PagingSource requires two generics. The first represents how the data on the next page should be loaded, such as an Int, and the next page should be loaded using an attribute of the last data, and the next page should be loaded using another type, such as String
- It is a suspend function decorated with suspend. It is easy to use the coroutine to load asynchronously.
- The LoadParams parameter has a key value that we can pull out to load the next page.
- The return value is a LoadResult, an exception was called
LoadResult.Error(e)
, it can be used under normal strong opening conditionLoadResult.Page
Method to set data retrieved from the network or database - PrevKey and nextKey represent load factors that need to be provided for the next up or down load, respectively. For example, if we load each page by increasing the number of pages, nextKey can be passed to the next page+1. If set to null, there is no data.
Create the Repository
class ArticleRepository {
fun getArticleData(a) = Pager(PagingConfig(pageSize = 20)){
ArticleDataSource()
}.flow
}
Copy the code
The code is minimal but there are two important objects: Pager and PagingData
- Pager is the main entry point to paging. It takes four parameters: PagingConfig, Key, RemoteMediator, and PagingSource. The first and fourth parameters are mandatory.
- PagingConfig is used to configure loading properties, such as how many bars count as a page, how far from the bottom to start the next page, the initial number of bars to load, and so on.
- PagingData is used to store the results of each pagination
- A flow is kotlin’s asynchronous data flow, similar to an RxJava Observable
Layer 2 ViewModel layer
Repository ultimately returns an asynchronous stream-wrapped PagingDataFlow
LiveData is commonly used in viewModels to interact with the UI layer, and the Flow extension function can be directly converted into a LiveData observable
class PagingViewModel:ViewModel() {
private val repository:ArticleRepository by lazy { ArticleRepository() }
/** * Pager page entry Each PagingData represents a page of data and finally calls asLiveData to convert the result into a listening LiveData */
fun getArticleData(a) = repository.getArticleData().asLiveData()
}
Copy the code
The UI layer
The UI layer is actually in our Activity, set up Adapter for RecycleView, set up data for Adater
class PagingActivity : AppCompatActivity() {
private val viewModel by lazy { ViewModelProvider(this).get(PagingViewModel::class.java)}
private val adapter: ArticleAdapter by lazy { ArticleAdapter() }
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_paging)
val refreshView:SmartRefreshLayout = findViewById(R.id.refreshView)
val recyclerView :RecyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter.withLoadStateFooter(PostsLoadStateAdapter(adapter))
// Get the data and render the UI
viewModel.getArticleData().observe(this, Observer {
lifecycleScope.launchWhenCreated {
adapter.submitData(it)
}
})
When the refresh is complete, close the refresh
lifecycleScope.launchWhenCreated {
@OptIn(ExperimentalCoroutinesApi::class)
adapter.loadStateFlow.collectLatest {
if(it.refresh !is LoadState.Loading){
refreshView.finishRefresh()
}
}
}
refreshView.setOnRefreshListener {
adapter.refresh()
}
}
}
Copy the code
- Create an instance of Adapter and set it to RecyclerView
- call
viewModel.getArticleData()
Method to get LiveData and listen for the returned data - Call the Adapter submitData method to trigger the rendering of the page. This method is a suspend method decorated by suspend, so call it in a lifetime coroutine. If you don’t want to put it in a coroutine you can call another method with two arguments
adapter.submitData(lifecycle,it)
Lifecycle will do
Refresh and retry
In Paging3.0, the method of calling refresh is much more convenient than in Paging2.x. It provides the method of refreshing directly, and also provides the method of retrying after loading data error.
The activity in front of the code, in the drop-down to refresh the control in the drop-down to monitor directly calling adapter. The refresh () method can complete the refresh, what time do you close to refresh the animation, you need to call adapter. LoadStateFlow. CollectLatest method to listen
lifecycleScope.launchWhenCreated {
@OptIn(ExperimentalCoroutinesApi::class)
adapter.loadStateFlow.collectLatest {
if(it.refresh ! is LoadState.Loading){ refreshView.finishRefresh() } } }Copy the code
Collect the state of the stream. If the Loading state is complete, you can turn off the animation.
PagingDataAdapter can set the header and bottom of the loading progress or load error layout, so that when in the loading state, can display the loading animation, load error can display the retry button. It’s also easy and comfortable to use.
You need to define a custom Adapter that inherits from the LoadStateAdapter and set it to the original Adapter
class PostsLoadStateAdapter(
private val adapter: ArticleAdapter
) : LoadStateAdapter<NetworkStateItemViewHolder>() {
override fun onBindViewHolder(holder: NetworkStateItemViewHolder, loadState: LoadState) {
holder.bindTo(loadState)
}
override fun onCreateViewHolder(
parent: ViewGroup,
loadState: LoadState
): NetworkStateItemViewHolder {
return NetworkStateItemViewHolder(parent) { adapter.retry() }
}
}
Copy the code
ViewHolder
class NetworkStateItemViewHolder(
parent: ViewGroup,
private val retryCallback: () -> Unit
) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.network_state_item, parent, false)) {private val progressBar = itemView.findViewById<ProgressBar>(R.id.progress_bar)
private val errorMsg = itemView.findViewById<TextView>(R.id.error_msg)
private val retry = itemView.findViewById<Button>(R.id.retry_button)
.also {
it.setOnClickListener { retryCallback() }
}
fun bindTo(loadState: LoadState) {
progressBar.isVisible = loadState is Loading
retry.isVisible = loadState isError errorMsg.isVisible = ! (loadStateas? Error)? .error? .message.isNullOrBlank() errorMsg.text = (loadStateas? Error)? .error? .message } }Copy the code
There are three loadstates: NotLoading, Loading, and Error. We can change the layout style of the bottom or top according to different states
Finally, in the activity Settings, add a bottom layout below
recyclerView.adapter = adapter.withLoadStateFooter(PostsLoadStateAdapter(adapter))
Copy the code
You can simply call the relevant adapter methods, add the bottom, add the header, and add both.
The simple use of Paging3.0 to this effect is as follows: