Android Jetpack Paging3分页库的使用(概述以及网络加载)
概述
使用分页库的好处
分页库包含以下功能:
- 分页数据的内存中缓存。这可确保您的应用在处理页面数据时有效地使用系统资源。
- 内置请求重复数据删除功能,确保您的应用有效地使用网络带宽和系统资源。
- 可配置
RecyclerView
适配器,当用户滚动到已加载数据的末尾时会自动请求数据。 - 对Kotlin协程和Flow以及
LiveData
的支持 。 - 内置的错误处理支持,包括刷新和重试功能。
架构设计
分页库隶属于安卓推荐架构设计的一部分。该库的组件在应用程序的使用中,涉及到三层结构。
- The repository layer
- The
ViewModel
layer - The UI layer
上图描述了在每个层上运行的分页库组件,以及它们如何协同工作以加载和显示分页数据。
储存库层
存储库层中的主要分页库组件为 PagingSource
。每个 PagingSource
对象都定义一个数据源以及如何从该源中检索数据。PagingSource
对象可以从任何单个源,包括网络源和本地数据库加载数据。
您可能会使用的另一个分页库组件是 RemoteMediator
。 RemoteMediator
对象用以处理分层数据源,例如有本地数据库缓存的网络数据源。
ViewModel层
该Pager
组件提供了一个公共API,用于构造PagingData
对象(基于PagingSource
,在响应流中公开的实例)和 PagingConfig
配置对象。
将ViewModel
图层连接到UI的组件是 PagingData
。PagingData
目的是用于分页数据的快照的容器。它查询一个 PagingSource
对象并存储结果。
UI层
UI层中的主要分页库组件是 PagingDataAdapter
,RecyclerView
用于处理分页数据的 适配器。
或者,您可以使用随附的 AsyncPagingDataDiffer
组件来构建自己的自定义适配器
**注意:**如果您的应用程序使用Compose for UI,请使用
androidx.paging:paging-compose
工件将Paging与UI层集成。要了解更多信息,请参阅的API文档collectAsLazyPagingItems()
。
加载并显示网络数据源的分页数据流
定义数据源
第一步是定义一个 PagingSource
实现以识别数据源。该PagingSource
API类包括 load()
方法,它必须重写,以指示如何从相应的数据源中检索分页数据。
PagingSource
直接使用该类可将Kotlin协程用于异步加载。
Paging库还提供了支持其他异步框架的类:
- 要使用RxJava,请改为
RxPagingSource
。 - 要
ListenableFuture
在Guava中使用,改为ListenableFuturePagingSource
。
选择键和值类型
PagingSource<Key, Value>
有两个类型参数:Key
和Value
。键定义用于加载数据的标识符,值是数据本身的类型。
例如,如果您通过传递Int
页码给 Retrofit,从网络加载的User
对象的分页数据。
应该选择Int
作为Key
类型,User
作为Value
类型。
定义PagingSource
以下示例实现了一个 PagingSource
按页码加载分页数据。该Key
类型是Int
与Value
类型 User
。
class ExamplePagingSource(
val backend: ExampleBackendService,
val query: String
) : PagingSource<Int, User>() {
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, User> {
try {
// Start refresh at page 1 if undefined.
val nextPageNumber = params.key ?: 1
val response = backend.searchUsers(query, nextPageNumber)
return LoadResult.Page(
data = response.users,
prevKey = null, // Only paging forward.
nextKey = response.nextPageNumber
)
} catch (e: Exception) {
// Handle errors in this block and return LoadResult.Error if it is an
// expected error (such as a network failure).
}
}
}
一个典型的PagingSource
实现将其构造函数中提供的参数传递给该load()
方法,以为查询加载适当的数据。
在上面的示例中,这些参数是:
-
backend
:提供数据的后端服务的实例。 -
query
:搜索查询以发送到表示的服务backend
。
该LoadParams
对象包含有关要执行的加载操作的信息。这包括要加载的key和要加载的项目数。
该LoadResult
对象包含加载操作的结果。LoadResult
是一个密封类,采用两种形式之一,具体取决于load()
调用是否成功:
-
如果加载成功,则返回一个
LoadResult.Page
对象。 -
如果加载不成功,则返回一个
LoadResult.Error
对象。
下图说明了load()
此示例中的功能如何为每个加载接收key并为后续加载提供key。
处理错误
加载数据的请求可能由于多种原因而失败,尤其是在通过网络加载时。
通过LoadResult.Error
从load()
方法返回对象来报告加载过程中遇到的错误 。
例如,您可以ExamplePagingSource
通过将以下内容添加到load()
方法中来捕获并报告上一示例中的加载错误:
catch (e: IOException) {
// IOException for network failures.
return LoadResult.Error(e)
} catch (e: HttpException) {
// HttpException for any non-2xx HTTP status codes.
return LoadResult.Error(e)
}
有关处理改造错误的更多信息,请参阅PagingSource
API参考中的示例 。
PagingSource
收集LoadResult.Error
对象并将其传递到UI,以便您可以对它们进行操作。有关在UI中公开加载状态的更多信息,请参阅显示加载状态。
设置PagingData流
接下来,您需要实现中的分页数据流PagingSource
。
通常,您应该在ViewModel
中设置数据流。 Pager
类提供方法,可以获得来自PagingSource
的响应流PagingData
对象。
分页库支持使用多个流类型,包括Flow
,LiveData
和Flowable
和Observable
类型从RxJava。
创建Pager
实例以设置响应流时,必须为该实例提供一个 PagingConfig
配置对象和一个函数,该函数告知Pager
如何获取实现的实例 PagingSource
:
val flow = Pager(
// Configure how data is loaded by passing additional properties to
// PagingConfig, such as prefetchDistance.
PagingConfig(pageSize = 20)
) {
ExamplePagingSource(backend, query)
}.flow
.cachedIn(viewModelScope)
cachedIn()
方法通过传入的CoroutineScope
提供数据流,并缓存加载的数据。
Pager
对象从PagingSource
中调用load()
方法,向其传入 LoadParams
对象并接收LoadResult
。
定义一个RecyclerView适配器
您还需要设置适配器以将数据接收到RecyclerView
列表中。
分页库PagingDataAdapter
为此提供了类。
定义一个可扩展的类PagingDataAdapter
。在该示例中, UserAdapter
继承PagingDataAdapter
作为RecyclerView
提供适配器, 列表类型User
,UserViewHolder
用作ViewHolder:
class UserAdapter(diffCallback: DiffUtil.ItemCallback<User>) :
PagingDataAdapter<User, UserViewHolder>(diffCallback) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): UserViewHolder {
return UserViewHolder(parent)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val item = getItem(position)
// Note that item may be null. ViewHolder must support binding a
// null item as a placeholder.
holder.bind(item)
}
}
您的适配器还必须定义onCreateViewHolder()
和 onBindViewHolder()
方法并指定 DiffUtil.ItemCallback
:
object UserComparator : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
// Id is unique.
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
}
在用户界面中显示分页数据
现在,您已经定义了一个PagingSource
,为您的应用程序生成了数据流PagingData
,并定义了一个PagingDataAdapter
,您可以将这些元素连接在一起并在活动中显示分页数据了。
在activity onCreate
或fragment的 onViewCreated
方法执行以下步骤:
- 创建您的
PagingDataAdapter
类的实例。 - 将
PagingDataAdapter
实例传递到RecyclerView
要显示分页数据的 列表。 - 观察
PagingData
流,并将每个生成的值传递给适配器的submitData()
方法。
val viewModel by viewModels<ExampleViewModel>()
val pagingAdapter = UserAdapter(UserComparator)
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = pagingAdapter
// Activities can use lifecycleScope directly, but Fragments should instead use
// viewLifecycleOwner.lifecycleScope.
lifecycleScope.launch {
viewModel.flow.collectLatest { pagingData ->
pagingAdapter.submitData(pagingData)
}
}
RecyclerView
现在能够显示来自数据源的分页数据,并在必要时自动加载另一页。
显示加载状态
分页库公开了加载状态,以通过LoadState
对象在UI中使用 。
LoadState
根据当前的加载状态,采用以下三种形式之一:
- 如果没有活动的加载操作且没有错误,
LoadState
则为LoadState.NotLoading
对象。 - 如果有活动的加载操作,
LoadState
则为LoadState.Loading
对象。 - 如果有错误,则
LoadState
是一个LoadState.Error
对象。
LoadState
在UI中有两种使用方法:
- 使用加载状态监听
- 使用特殊的列表适配器。
使用监听器获取加载状态
要获取通常在UI中使用的加载状态,请 PagingDataAdapter
包含addLoadStateListener()
方法。
// Activities can use lifecycleScope directly, but Fragments should instead use
// viewLifecycleOwner.lifecycleScope.
lifecycleScope.launch {
pagingAdapter.loadStateFlow.collectLatest { loadStates ->
progressBar.isVisible = loadStates.refresh is LoadState.Loading
retry.isVisible = loadState.refresh !is LoadState.Loading
errorMsg.isVisible = loadState.refresh is LoadState.Error
}
}
使用适配器显示加载状态
分页库提供了另一个列表适配器 LoadStateAdapter
,其目的是直接在显示的页面数据列表中显示加载状态。
首先,创建一个实现的类LoadStateAdapter
,并定义 onCreateViewHolder()
和onBindViewHolder()
方法:
class LoadStateViewHolder(
parent: ViewGroup,
retry: () -> Unit
) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.load_state_item, parent, false)
) {
private val binding = LoadStateItemBinding.bind(itemView)
private val progressBar: ProgressBar = binding.progressBar
private val errorMsg: TextView = binding.errorMsg
private val retry: Button = binding.retryButton
.also {
it.setOnClickListener { retry() }
}
fun bind(loadState: LoadState) {
if (loadState is LoadState.Error) {
errorMsg.text = loadState.error.localizedMessage
}
progressBar.isVisible = loadState is LoadState.Loading
retry.isVisible = loadState is LoadState.Error
errorMsg.isVisible = loadState is LoadState.Error
}
}
// Adapter that displays a loading spinner when
// state = LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter(
private val retry: () -> Unit
) : LoadStateAdapter<LoadStateViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
loadState: LoadState
) = LoadStateViewHolder(parent, retry)
override fun onBindViewHolder(
holder: LoadStateViewHolder,
loadState: LoadState
) = holder.bind(loadState)
}
然后,withLoadStateHeaderAndFooter()
从您的PagingDataAdapter
对象中调用该方法 :
pagingAdapter
.withLoadStateHeaderAndFooter(
header = ExampleLoadStateAdapter(adapter::retry),
footer = ExampleLoadStateAdapter(adapter::retry)
)
本文地址:https://blog.csdn.net/u012591761/article/details/110197209