许久没有更新Jetpack系列的文章了,本篇文章为大家分享分页库Paging3的使用,如果你还没有看过我Jetpack其他的文章,可以移步至链接:
为了能让自己更加快速的学习,英语菜鸡的我最近一直在恶补英语,直接看原版官方文档学习的会更快,皇天不负有心人,经过两个星期的坚持,现在官方文档上的10个单词我已经认识两个了~
想想我们之前的业务中,实现分页加载需要怎么处理?一般我们都是自己封装RecycleView或者使用XRecycleView这种第三方库去做,而Paging 就是Google为我们提供的分页功能的标准库,这样我们就无须自己去基于RecycleView实现分页功能,并且Paging为我们提供了许多可配置选项,使得分页功能更加灵活。而Paging3是Paging库当前的最新版本,仍处于测试版本,相比较于Paging2的使用就简洁多了。
首先我们新建项目,在gradle中引用paging库如下:
def paging_version = "3.0.0-alpha07"
implementation "androidx.paging:paging-runtime:$paging_version"
testImplementation "androidx.paging:paging-common:$paging_version"
项目示例,我们使用Kotlin语言并且使用了协程和Flow,所以也需要添加协程的库如下:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7-mpp-dev-11'
如果你还不了解协程和Flow可以先移步至前两篇文章
在官方文档中也给出了我们Paging在架构中的使用图
通过上图我们也可以清晰的看出来,Paging在仓库层、ViewModel和UI层都有具体的表现,接下来我们通过一个示例来逐步讲解Paging是如何在项目架构中工作的。
API接口我们使用 「鸿洋」大佬 「玩Android」中的查询每日一问接口 :https://wanandroid.com/wenda/list/1/json
这里我们已经写好了RetrofitService类用于创建网络请求的service代码如下所示:
object RetrofitService {
/**
* okhttp client
*/
lateinit var okHttpClient: OkHttpClient
/**
* 主Url地址
*/
private const val BASEAPI = "https://www.wanandroid.com/";
/**
* 创建service对象
*/
fun <T> createService(mClass: Class<T>): T {
val builder: OkHttpClient.Builder = OkHttpClient.Builder();
okHttpClient = builder.build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASEAPI)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
return retrofit.create(mClass) as T
}
}
和 DataApi接口,这里我们将方法声明为挂起函数,便于在协程中调用
interface DataApi {
/**
* 获取数据
*/
@GET("wenda/list/{pageId}/json")
suspend fun getData(@Path("pageId") pageId:Int): DemoReqData
}
首先我们来定义数据源DataSource继承自PagingSource,代码如下所示:
class DataSource():PagingSource<Int,DemoReqData.DataBean.DatasBean>(){
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DemoReqData.DataBean.DatasBean> {
TODO("Not yet implemented")
}
}
我们可以看到PagingSource中有两个参数Key 和 Value,这里Key我们定义为Int类型Value DemoReqData 是接口返回数据对应的实体类,这里的意思就是
我们传Int类型的值(如页码)得到返回的数据信息DemoReqData对象。
这里需要提醒的是如果你使用的不是Kotlin 协程而是Java,则需要继承对应的PagingSource如RxPagingSource或ListenableFuturePagingSource。
DataSource为我们自动生成了load方法,我们主要的请求操作就在load方法中完成。主要代码如下所示:
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DemoReqData.DataBean.DatasBean> {
return try {
//页码未定义置为1
var currentPage = params.key ?: 1
//仓库层请求数据
var demoReqData = DataRespority().loadData(currentPage)
//当前页码 小于 总页码 页面加1
var nextPage = if (currentPage < demoReqData?.data?.pageCount ?: 0) {
currentPage + 1
} else {
//没有更多数据
null
}
if (demoReqData != null) {
LoadResult.Page(
data = demoReqData.data.datas,
prevKey = null,
nextKey = nextPage
)
} else {
LoadResult.Error(throwable = Throwable())
}
} catch (e: Exception) {
LoadResult.Error(throwable = e)
}
}
上面代码我们可以看到在datasource中我们通过DataRespority()仓库层,去请求数据,如果没有更多数据就返回null,最后使用 LoadResult.Page将结果返回,如果加载失败则用LoadResult.Error返回,由于 LoadResult.Page中的data 必须是非空类型的,所以我们需要判断返回是否为null。
接下来我们看下DataRespority仓库层的代码,代码比较简单,如下所示:
class DataRespority {
private var netWork = RetrofitService.createService(
DataApi::class.java
)
/**
* 查询护理数据
*/
suspend fun loadData(
pageId: Int
): DemoReqData? {
return try {
netWork.getData(pageId)
} catch (e: Exception) {
//在这里处理或捕获异常
null
}
}
}
Load调用官方给出的流程如下所示:
从上图可以知道,load的方法 是我们通过Paging的配置自动触发的,不需要我们每次去调用,那么我们如何来使用DataSource呢?
The Pager
object calls the load()
method from the PagingSource
object, providing it with the LoadParams
object and receiving the LoadResult
object in return.
这句话翻译过来的意思就是:Pager对象从PagingSource对象调用load()方法,为它提供LoadParams对象,并作为回报接收LoadResult对象。
所以我们在创建viewModel对象,并创建pager对象从而调用PagingSource方法 ,代码如下所示:
class MainActivityViewModel : ViewModel() {
/**
* 获取数据
*/
fun getData() = Pager(PagingConfig(pageSize = 1)) {
DataSource()
}.flow
}
在viewmodel中我们定义了一个getData的方法,Pager中通过配置PagingConfig来实现特殊的定制,我们来看下PagingConfig中的参数如下:
pageSize:定义从 PagingSource 一次加载的项目数。
prefetchDistance:预取距离,简单解释就是 当距离底部还有多远的时候自动加载下一页,即自动调用load方法,默认值和pageSize相等
enablePlaceholders:是否显示占位符,当网络不好的时候,可以考到页面的框架,从而提升用户体验
还有一些其他参数这里就不一一介绍了,从构造方法的源码中可以看出pageSize这个参数是必填的,其他的是可选项,所以我们这里传了1
这一步,和我们平时定义普通的RecycleViewAdapter没有太大的区别,只是我们继承的是PagingDataAdapter,主要代码如下所示:
class DataRecycleViewAdapter :
PagingDataAdapter<DemoReqData.DataBean.DatasBean, RecyclerView.ViewHolder>(object :
DiffUtil.ItemCallback<DemoReqData.DataBean.DatasBean>() {
override fun areItemsTheSame(
oldItem: DemoReqData.DataBean.DatasBean,
newItem: DemoReqData.DataBean.DatasBean
): Boolean {
return oldItem.id == newItem.id
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(
oldItem: DemoReqData.DataBean.DatasBean,
newItem: DemoReqData.DataBean.DatasBean
): Boolean {
return oldItem == newItem
}
}) {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
var dataBean = getItem(position)
(holder as DataViewHolder).binding.demoReaData = dataBean
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder {
return TestViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.health_item_test,
parent,
false
)
)
}
inner class DataViewHolder(private val dataBindingUtil: ItemDataBinding) :
RecyclerView.ViewHolder(dataBindingUtil.root) {
var binding = dataBindingUtil
}
}
这里我们要提醒的是DiffUtil这个参数,用于计算列表中两个非空项目之间的差异的回调。无特殊情况一般都是固定写法。
到这里,基本工作已经差不多了,当然我们说的差不多了只是快能看到成果了,其中需要讲解的地方还有很多,最后一步我们在view中请求数据,并将结果绑定在adapter上
我们在View代码中调用viewModel中的getData方法,代码如下所示:
val manager = LinearLayoutManager(this)
rv_data.layoutManager = manager
rv_data.adapter = dataRecycleViewAdapter
btn_get.setOnClickListener {
lifecycleScope.launch {
mainActivityViewModel.getData().collectLatest {
dataRecycleViewAdapter.submitData(it)
}
}
}
我们在协程中调用getData方法,接收最新的数据,通过PagingAdapter的submitData方法为adapter提供数据,运行结果如下所示(忽略丑陋的UI.jpg)
当我们往下滑动时,当底部还剩1个(pageSize)数据的时候会自动加载下一页。
当然对于这个接口不需要传pageSize,所以返回的数据大小并不会受pageSize的影响,如此一来,我们就使用Paging3 完成了简单的数据分页请求。
当然在实际开发中,上面的代码会有很多问题,以及Paging为我们提供了更多且有用的功能,我们将在下一篇中继续为大家分享~
Android Jetpack系列 之分页库Paging3(二)~敬请期待