【问题标题】:Android Paging 3 library PagingSource invalidation, causes the list to jump due to wrong refresh key (not using room)Android Paging 3 库 PagingSource 失效,刷新键错误导致列表跳转(不使用房间)
【发布时间】:2021-11-26 16:13:18
【问题描述】:

由于我目前正在处理一个带有自定义数据库(不是 Room)的项目,我正在测试我们是否可以在项目中使用 Paging 3 库。 但是,我遇到了一个问题,如果您更改数据并因此使分页源无效,则列表的重新创建是错误的并跳转到不同的位置。发生这种情况是因为 Refresh Key 计算似乎错误,这很可能是由于初始加载加载了三页的数据,但将其放入一页中。

默认分页源如下所示:

    override fun getRefreshKey(state: PagingState<Int, CustomData>): Int? {
        // Try to find the page key of the closest page to anchorPosition, from
        // either the prevKey or the nextKey, but you need to handle nullability
        // here:
        //  * prevKey == null -> anchorPage is the first page.
        //  * nextKey == null -> anchorPage is the last page.
        //  * both prevKey and nextKey null -> anchorPage is the initial page, so
        //    just return null.
        return state.anchorPosition?.let { anchorPosition ->
            val anchorPage = state.closestPageToPosition(anchorPosition)
            anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
        }
    }

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CustomData> {
    var pagePosition = params.key ?: STARTING_PAGE_INDEX
    var loadSize = params.loadSize
    return try {
        val dataResult = dataRepository.getPagedData(
            pagePosition = pagePosition,
            loadSize = loadSize,
            pageSize = pageSize
        )
        val nextKey = if (dataResult.isEmpty() || dataResult.size < pageSize) {
            null
        } else {
            pagePosition + (loadSize / pageSize)
        }
        Log.i(
            "RoomFreePagingSource",
            "page $pagePosition with size $loadSize publish ${dataResult.size} routes"
        )
        return LoadResult.Page(
            data = dataResult,
            prevKey = if (pagePosition == STARTING_PAGE_INDEX) null else pagePosition - 1,
            nextKey = nextKey
        )

    } catch (exception: Exception) {
        LoadResult.Error(exception)
    }
}

dataRepository.getPagedData() 函数只是访问内存中的列表并返回列表的子部分,模拟分页数据。 为了完整起见,这里是这个函数的实现:

fun getPagedData(pageSize:Int, loadSize:Int, pagePosition: Int): List<CustomData>{
    val startIndex = pagePosition * pageSize
    val endIndexExl =startIndex + loadSize
    return data.safeSubList(startIndex,endIndexExl).map { it.copy() }
}

private fun <T> List<T>.safeSubList(fromIndex: Int, toIndex: Int) : List<T>{
    // only returns list with valid range, to not throw exception
    if(fromIndex>= this.size)
        return emptyList()
    val endIndex = if(toIndex> this.size) this.size else toIndex
    return subList(fromIndex,endIndex)
}

我目前面临的主要问题是,getRefreshKey 函数没有返回正确的刷新页面键,导致刷新错误页面,列表跳转到加​​载页面。

  • 例如,如果您每页有 10 个项目。
  • 首页 0 包含 30 项
  • 下一页将是 3 并且包含 10 项。
  • 如果您不滚动(仅查看前 7 项)并使其无效,则 anchorPosition 将为 7,刷新键将为 2。 (anchorPage.prevKey = null => anchorPage.nextKey = 3 => 3-1 是 2) 但是,此时我们希望加载第 0 页而不是第 2 页。

知道问题的原因后,我尝试调整默认实现来解决它,并提出了许多不同的版本。下面的一个目前效果最好,但仍然会不时引起跳跃。并且有时列表的一部分会闪烁,这可能是由于没有足够的项目被提取来填充视口。

override fun getRefreshKey(state: PagingState<Int, CustomData>): Int? {
    return state.anchorPosition?.let { anchorPosition ->
        val closestPage = state.closestPageToPosition(anchorPosition)?.prevKey
            ?: STARTING_PAGE_INDEX
        val refKey = if(anchorPosition>(closestPage)*pageSize + pageSize)
            closestPage+1
        else
            closestPage

        Log.i(
            "RoomFreePagingSource",
            "getRefreshKey $refKey from anchorPosition $anchorPosition closestPage $closestPage"
        )
        refKey
    }
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CustomData> {
    var pagePosition = params.key ?: STARTING_PAGE_INDEX
    var loadSize = pageSize
    return try {
        when (params) {
            is LoadParams.Refresh -> {
                if (pagePosition > STARTING_PAGE_INDEX) {
                    loadSize *= 3
                } else if (pagePosition == STARTING_PAGE_INDEX) {
                    loadSize *= 2
                }
            }
            else -> {}
        }
        val dataResult = dataRepository.getPagedData(
            pagePosition = pagePosition,
            loadSize = loadSize,
            pageSize = pageSize
        )
        val nextKey = if (dataResult.isEmpty() || dataResult.size < pageSize) {
            null
        } else {
            pagePosition + (loadSize / pageSize)
        }
        Log.i(
            "RouteRepository",
            "page $pagePosition with size $loadSize publish ${dataResult.size} routes"
        )
        return LoadResult.Page(
            data = dataResult,
            prevKey = if (pagePosition == STARTING_PAGE_INDEX) null else pagePosition - 1,
            nextKey = nextKey
        )

    } catch (exception: Exception) {
        LoadResult.Error(exception)
    }
}

理论上,我发现最好的解决方案是像这样anchorPosition/pageSize 计算刷新PageKey,然后加载这个页面,它之前和之后的页面。但是,像这样计算刷新键不起作用,因为 anchorPosition 不是列表中项目的实际位置。在一些失效后,即使您当前正在查看列表中的第 140 项,锚点也可能为 5。

总结一下:

当我不使用 Room 而是使用另一个数据源时,如何在失效后计算正确的刷新页面,例如在此示例中我通过 getPagedData 访问的内存列表中?

【问题讨论】:

  • 我有一个 PagingSource 的自定义 Impl,你的似乎是一样的,但如果你留下了一些东西,你可以 check that out

标签: android android-paging android-paging-library android-paging-3


【解决方案1】:

您可以在创建 Pager 时在 PagingConfig 对象中定义初始加载大小。喜欢

Pager(
    config = PagingConfig(pageSize = PAGE_SIZE, initialLoadSize = PAGE_SIZE),
    initialKey = INITIAL_PAGE,
    remoteMediator = mediator,
    pagingSourceFactory = {
        ...
    })

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-12
    • 2021-11-25
    • 2021-11-06
    • 2021-10-05
    • 1970-01-01
    • 2021-11-20
    • 2023-01-18
    • 2020-10-09
    相关资源
    最近更新 更多