重点 (Top highlight)
Most apps displays a large list of data to the users, but at a particular time the user sees only a small chunk of data in your app, so fetching all the data from the network is not an efficient solution. The solution is to fetch small chunks of data at a time, and as soon as the user reach at the end of the list, then the app will load more data. This is called Paging.
大多数应用程序会向用户显示大量数据,但是在特定时间,用户只能看到应用程序中的一小部分数据,因此从网络中获取所有数据并不是一种有效的解决方案。 解决方案是一次获取一小块数据,并且一旦用户到达列表末尾,应用程序就会加载更多数据 。 这称为分页。
With the release of Android 11 beta, Google also updated the Paging architectural components library for android. Paging 3 greatly simplifies implementing paging in your android app.
随着Android 11 Beta的发布,Google还更新了Android的Paging建筑组件库。 分页3大大简化了在Android应用中实现分页的过程。
分页3.0的新增功能 (What’s New in Paging 3.0)
Paging 3.0 is significantly different from the earlier versions of Paging Library. Some of the new features of Paging 3.0 includes:
Paging 3.0与早期版本的Paging Library明显不同。 Paging 3.0的一些新功能包括:
- Support for Kotlin coroutines, Flow, as well as LiveData and RxJava. 支持Kotlin协程,Flow以及LiveData和RxJava。
- Built in support for error handling, refresh and retry functionality. 内置对错误处理,刷新和重试功能的支持。
- Built in support for loading state headers, footers and list separators. 内置支持加载状态页眉,页脚和列表分隔符。
- In memory caching of data, ensures efficient use of system resources. 在数据的内存缓存中,确保有效利用系统资源。
- Prevents api request duplication. 防止api请求重复。
- Improvements to the repository layer, including cancellation support and a simplified data source interface. 对存储库层的改进,包括取消支持和简化的数据源界面。
设置您的项目 (Setup Your Project)
To use the Paging 3.0 library, add it to your app level build.gradle
file.
要使用Paging 3.0库,请将其添加到应用程序级别的build.gradle
文件中。
dependencies {
def paging_version = "3.0.0-alpha03"
implementation "androidx.paging:paging-runtime:$paging_version"
}
If you want to use RxJava
or LiveData
, you need to also include:
如果要使用RxJava
或LiveData
,还需要包括:
// optional - RxJava2 support
implementation "androidx.paging:paging-rxjava2:$paging_version"
// optional - Guava ListenableFuture support
implementation "androidx.paging:paging-guava:$paging_version"
创建数据源 (Create a Data Source)
Unlike the previous versions of Paging library, in Paging 3.0 we have to implement a PagingSource<Key, Value>
to define a data source. The PagingSource
takes two parameters a Key
and a Value
. The Key
parameter is the identifier of the data to be loaded such as page number and the Value
is the type of the data itself.
与PagingSource<Key, Value>
版本的Paging库不同,在Paging 3.0中,我们必须实现PagingSource<Key, Value>
来定义数据源。 PagingSource
具有两个参数Key
和Value
。 Key
参数是要加载的数据的标识符,例如页码,而Value
是数据本身的类型。
class MoviePagingSource(
val movieApiService: MovieApiService,
) : PagingSource<Int, Movie>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> {
try {
// Start refresh at page 1 if undefined.
val nextPage = params.key ?: 1
val response = movieApiService.getPopularMovies(nextPage)
return LoadResult.Page(
data = response.movies,
prevKey = if (nextPage == 1) null else nextPage - 1,
nextKey = response.page + 1
)
} catch (e: Exception) {
return LoadResult.Error(e)
}
}
}
Notice the overridden
load()
function is asuspend
function so we can make api requests here to get data from a network or a room database easily.注意,重写的
load()
函数是一个suspend
函数,因此我们可以在此处发出api请求,以轻松地从网络或会议室数据库获取数据。The
LoadParams
object holds the information about the load operation such askey
and page size.LoadParams
对象保存有关加载操作的信息,例如key
和页面大小。If the api request is successful, then we will return the response data wrapped in a
LoadResult.Page
object along with the previous and next keys.如果api请求成功,则我们将返回包装在
LoadResult.Page
对象中的响应数据以及上一个和下一个键。If the api request is unsuccessful then we will return the occurred exception wrapped in a
LoadResult.Error
object.如果api请求失败,那么我们将返回包装在
LoadResult.Error
对象中的发生的异常。
The figure below shows exactly how the load()
function recieves keys from the previous load and provides the keys for the next load.
下图准确显示了load()
函数如何从上一次加载中获取键并为下一次加载提供键。
在ViewModel中获取PagingData (Get the PagingData in ViewModel)
Now we will create an instance of Pager
in our viewmodel to get a stream of data from the MoviePagingSource
that we just created.
现在,我们将在视图模型中创建Pager
实例,以从刚刚创建的MoviePagingSource
中获取数据流。
val movies: Flow<PagingData<Movie>> = Pager(PagingConfig(pageSize = 20)) {
MoviePagingSource(movieApiService)
}.flow
.cachedIn(viewModelScope)
The
Pager
object calls theload()
method from theMoviePagingSource
object, providing it with theLoadParams
object and receiving theLoadResult
object in return.Pager
对象从MoviePagingSource
对象调用load()
方法,为它提供LoadParams
对象,并作为回报接收LoadResult
对象。We also have to provide configurations such as
pageSize
with thePagingConfig
object.我们还必须通过
PagingConfig
对象提供诸如pageSize
之PagingConfig
配置。The
cachedIn(viewModelScope)
caches the data from theMoviePagingSource
to survive the screen orientation changes.该
cachedIn(viewModelScope)
缓存从数据MoviePagingSource
生存的屏幕方向的变化。
在RecyclerView中显示数据 (Display data in RecyclerView)
First we have to create a RecyclerView
adapter class which extends from the PagingDataAdapter
. This is the same as a normal RecyclerView
adapter. The PagingDataAdapter
takes two parameters, the first one is the type of the data(which in our case is the Movie
object), and the second one is a RecyclerView.ViewHolder
.
首先,我们必须创建一个从PagingDataAdapter
扩展的RecyclerView
适配器类。 这与普通的RecyclerView
适配器相同。 PagingDataAdapter
具有两个参数,第一个是数据的类型(在我们的示例中是Movie
对象),第二个是RecyclerView.ViewHolder
。
class MovieAdapter :
PagingDataAdapter<Movie, MovieAdapter.MovieViewHolder>(MovieComparator){
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
holder.itemView.movie_title.text = getItem(position)!!.title
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
return MovieViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.movie_item, parent, false)
)
}
class MovieViewHolder(view: View) : RecyclerView.ViewHolder(view)
object MovieComparator : DiffUtil.ItemCallback<Movie>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
// Id is unique.
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem == newItem
}
}
}
Finally, in OnActivityCreated()
of our fragment, we can collect the data from PagingData
and then submit it into our MovieAdapter
.
最后,在片段的OnActivityCreated()
中,我们可以从PagingData
收集数据,然后将其提交到MovieAdapter
。
movieAdapter = MovieAdapter()
movie_recyclerView.apply {
layoutManager = LinearLayoutManager(context)
setHasFixedSize(true)
adapter = movieAdapter
}
lifecycleOwner.lifecycleScope.launch {
viewModel.movies.collectLatest {
movieAdapter.submitData(it)
}
}
The RecyclerView
list now displays data from the data source and automatically loads more data when we reach at the end of the list.
现在, RecyclerView
列表显示来自数据源的数据,并在到达列表末尾时自动加载更多数据。
显示LoadState和错误消息 (Display LoadState and Error messages)
The Paging 3.0 library has support for displaying loading states and handling errors. The Paging library exposes the loading state for use in the UI through the LoadState
object.
Paging 3.0库支持显示加载状态和处理错误。 分页库公开了加载状态,以通过LoadState
对象在UI中使用。
If the
LoadState
is aLoadState.NotLoading
object, then there is no active load operation and no errors.如果
LoadState
是LoadState.NotLoading
对象,则没有活动的加载操作,也没有错误。If the
LoadState
is aLoadState
.Loading
object, then there is an active load operation.如果
LoadState
是一个LoadState
.Loading
对象,则有一个活动的加载操作。If the
LoadState
is aLoadState.Error
object, then there is an error.如果
LoadState
是LoadState.Error
对象,则存在错误。
We can attach a addLoadStateListener()
in our MovieAdapter
to listen for LoadState
updates.
我们可以附加一个addLoadStateListener()
在我们的MovieAdapter
监听LoadState
更新。
movieAdapter.addLoadStateListener { loadState ->
/**
This code is taken from https://medium.com/@yash786agg/jetpack-paging-3-0-android-bae37a56b92d
**/
if (loadState.refresh is LoadState.Loading){
progressBar.visibility = View.VISIBLE
}
else{
progressBar.visibility = View.GONE
// getting the error
val error = when {
loadState.prepend is LoadState.Error -> loadState.prepend as LoadState.Error
loadState.append is LoadState.Error -> loadState.append as LoadState.Error
loadState.refresh is LoadState.Error -> loadState.refresh as LoadState.Error
else -> null
}
errorState?.let {
Toast.makeText(this, it.error.message, Toast.LENGTH_LONG).show()
}
}
}
In the next article, we learn how to create header or footers to show the loading state in the RecyclerView
itself, and then we will learn to create list separators.
在下一篇文章中,我们将学习如何创建页眉或页脚以在RecyclerView
本身中显示加载状态,然后将学习创建列表分隔符。
结论 (Conclusion)
The Paging 3.0 architectural components library is a major update over the previous versions of paging library, and it is completely rewritten from the previous versions of Paging library. It has complete support for the Kotlin coroutines
and other reactive streams such as RxJava
and LiveData
. It also has inbuilt error handling functionality and support for managing loading states which makes implementing paging in our app super easy.
Paging 3.0体系结构组件库是对早期版本的页面库的重大更新,并且已从先前版本的Paging库中完全重写。 它完全支持Kotlin coroutines
和其他React流,例如RxJava
和LiveData
。 它还具有内置的错误处理功能,并支持管理加载状态,这使得在我们的应用程序中实现分页非常容易。
其他部分 (Other parts)
翻译自: https://proandroiddev.com/how-to-use-the-paging-3-library-in-android-5d128bb5b1d8