Jetpack Compose Paging3+retrofit2封装上拉加载(一)

3,189 阅读3分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

添加依赖

implementation "androidx.paging:paging-runtime:3.0.0-beta02"
implementation "androidx.paging:paging-compose:1.0.0-alpha08"

retrofit2的使用方法这里就赘述了,我尽量演示地通俗易懂。
retrofit2的流程是

初始化Retrofit
创建一个全局可用的retrofit对象,通常都是单例模式
编写请求接口 构建repository

推荐一篇文章[Android帅次]的
❤️Android OkHttp+Retrofit+Rxjava+Hilt实现网络请求框架❤️

回到正题

Paging3的使用

paging.pager

Primary entry point into Paging; constructor for a reactive stream of PagingData.
翻译一下就是
分页的主要入口点;分页数据的反应流的构造函数。

这家伙长这样

public class Pager<Key : Any, Value : Any>
// 通过构造函数参数传播到公共 API。
@ExperimentalPagingApi constructor(
    config: PagingConfig,
    initialKey: Key? = null,
    remoteMediator: RemoteMediator<Key, Value>?,
    pagingSourceFactory: () -> PagingSource<Key, Value>
)

有四个参数

PagingConfig

先说PagingConfig 有5个配置

PagingConfig参数默认值
pageSize: Int
prefetchDistance: IntpageSize
enablePlaceholders: Booleantrue
initialLoadSize: IntpageSize * 3
maxSize: Int无限

PagingConfig: 从分页源加载内容时,用于配置分页器内加载行为的对象

定义从PagingSource一次加载的项目数。

pageSize

应该是屏幕上可见项目数的几倍。 注意:pageSize用于通知PagingSource。加载参数。loadSize,但不强制执行。PagingSource可能完全忽略此值,但仍返回有效页面。

prefetchDistance

prefetchDistance 默认等于 = pageSize 定义如果分页源提供空占位符,则分页数据是否可以显示空占位符。

如果满足以下两个条件,PagingData将为尚未加载的内容显示空占位符:
1.它的PagingSource可以计算所有已卸载的项(以便知道要显示的空值的数量)。 2.enablePlaceholders设置为true

enablePlaceholders

为PagingSource的初始加载定义请求的加载大小,通常大于pageSize,因此在第一次加载数据时,加载的内容范围足够大,可以覆盖小的滚动条。

注意:initialLoadSize用于通知PagingSource。加载参数。loadSize,但不强制执行。PagingSource可能会完全忽略此值,但仍会返回有效的初始页面

initialLoadSize

定义在删除页面之前可以加载到PagingData中的最大项目数。

maxSize

最大值嘛

initialKey

新建一个实体来装PagingConfig的配置

data class AppPagingConfig

PagingSource

我们用Pager的 config和initialKey就可以了

先重写loadgetRefreshKey方法 抽出来做进一步的封装

config: AppPagingConfig = AppPagingConfig()
initialKey: K? = null
...
Pager(
    config = baseConfig,
    initialKey = initialKey
) {
    object : PagingSource<K, V>() {
        override suspend fun load(params: LoadParams<K>): LoadResult<K, V> {
            return loadData.invoke(params)
        }
        override fun getRefreshKey(state: PagingState<K, V>): K? {
            return initialKey
        }
    }
}.flow.cachedIn(viewModelScope)
loadData: suspend (PagingSource.LoadParams<K>) -> PagingSource.LoadResult<K, V>

在loadData里面做网络请求的逻辑 请求成功时返回LoadResult.Page,失败时返回LoadResult.Error

完整代码

fun <T : Any> ViewModel.simplePager(
    config: AppPagingConfig = AppPagingConfig(),
    callAction: suspend (page: Int) -> BasicBean<ListWrapper<T>>
): Flow<PagingData<T>> {
    return pager(config, 0) {
        val page = it.key ?: 0
        val response = try {
            //请求到的数据 
            HttpResult.Success(callAction.invoke(page))
        } catch (e: Exception) {
            
            HttpResult.Error(e)
        }
        when (response) {
            is HttpResult.Success -> {
                val data = response.result.data
                val hasNotNext = (data!!.datas.size < it.loadSize) && (data.over)
                //LoadResult.Page详细可看上篇文章
                //data= 请求到的数据内容
                PagingSource.LoadResult.Page(
                    data = response.result.data!!.datas,
                    prevKey = if (page - 1 > 0) page - 1 else null,
                    nextKey = if (hasNotNext) null else page + 1
                )
            }
            is HttpResult.Error -> {
                PagingSource.LoadResult.Error(response.exception)
            }
        }
    }
}

fun <K : Any, V : Any> ViewModel.pager(
    config: AppPagingConfig = AppPagingConfig(),
    initialKey: K? = null,
    loadData: suspend (PagingSource.LoadParams<K>) -> PagingSource.LoadResult<K, V>
): Flow<PagingData<V>> {
    val baseConfig = PagingConfig(
        config.pageSize,
        initialLoadSize = config.initialLoadSize,
        prefetchDistance = config.prefetchDistance,
        maxSize = config.maxSize,
        enablePlaceholders = config.enablePlaceholders
    )
    return Pager(
        config = baseConfig,
        initialKey = initialKey
    ) {
        object : PagingSource<K, V>() {
            override suspend fun load(params: LoadParams<K>): LoadResult<K, V> {
                return loadData.invoke(params)
            }

            override fun getRefreshKey(state: PagingState<K, V>): K? {
                return initialKey
            }

        }
    }.flow.cachedIn(viewModelScope)
}
data class AppPagingConfig(
    val pageSize: Int = 20,
    val initialLoadSize: Int = 20,
    val prefetchDistance:Int = 1,
    val maxSize:Int = PagingConfig.MAX_SIZE_UNBOUNDED,
    val enablePlaceholders:Boolean = false
)

使用方法

在ViewModel里配置paging的数据 用flow将数据转换为Flow的形式,cachedIn(viewModelScope)将数据缓存在viewmodel

private val pager by lazy {
    simplePager {
       //it = 加载的页数
       //根据页数来请求数据
        service.getSquareData(it)
    }.cachedIn(viewModelScope)
}

var viewStates by mutableStateOf(ViewState(pagingData = pager))
    private set


data class ViewState(  
    val pagingData: PagingBean
)
typealias PagingBean = Flow<PagingData<实体类>>

渲染UI

val viewStates = remember { viewModel.viewStates }

val squareData = viewStates.pagingData.collectAsLazyPagingItems()
LazyColumn() {
    itemsIndexed(squareData) { _, item ->
       Text(text = item.data)
    }
}

下一篇 配和SwipeRefresh 下拉刷新一起使用