【问题标题】:@PrimaryKey of Room Table creates an unexpected behaviour in Android PagingRoom Table 的 @PrimaryKey 在 Android Paging 中产生意外行为
【发布时间】:2020-11-12 01:03:45
【问题描述】:

我正在使用 Room 数据库按照此 Codelab 教程实现带有缓存的 Paging:-https://codelabs.developers.google.com/codelabs/android-paging/#13

然而,在实现缓存部分时,我在recyclerview 中附加下一页时遇到了一些奇怪的行为。

RemoteMediator 仅在连续无限循环中获取第二页数据。它不是为下一页获取数据,而是为第二页连续获取数据。

我发现这是由于表的@PrimaryKey 而发生的。我在表格中有Int 作为@PrimaryKey,它有5 个字符长。

在 codelab 教程中也实现了相同的功能,他们也将Int 作为@PrimaryKey。但它们至少有 7 个字符长的随机主键。它工作得很好。为了验证这一点,我通过添加使我的 @PrimaryKey 长度约为 15 个字符

id = id + System.currentTimeMillis()

并且在这样做之后它完美地工作。但在实际场景中我无法修改 id。

那么,Room 表的@PrimayKey 是否有任何限制(即主键必须至少有 7 个字符)?还是 @PrimaryKey 必须在 Room 中随机化?还是我在这里做错了什么?

这是代码和 JSON。

JSON 格式

[
    { 
        "id": 24087,
        "date": "2020-07-15T11:20:00",
        "link": "https://www.somesite.com/24534-eu-covid-19-stimulus-negotiations-and-its-historic-backdrop-2020-07-21/",
        "title": {
            "rendered": "EU Covid-19 Stimulus Negotiations And Its Historic Backdrop"
        },
        "excerpt": {
            "rendered": "After a long debate between the opposing factions, the European Union agreed on a recovery fund to aid the economy amidst the Covid-19 pandemic..."
        }
    },
    {..},
    {..}
]

Story.kt

@Entity(tableName = "stories")
data class Story(
    @PrimaryKey @field:SerializedName("id") var storyId: Long,
    @field:SerializedName("date") val date: String,
    @field:SerializedName("link") val link: String,
    @Embedded(prefix = "title_") @field:SerializedName("title") val title: Title,
    @Embedded(prefix = "excerpt_") @field:SerializedName("excerpt") val excerpt: Excerpt    
)

data class Title(
    val rendered: String
)

data class Excerpt(
    val rendered: String
)

RemoteMediator.kt

@OptIn(ExperimentalPagingApi::class)
class StoryRemoteMediator(
    private val category: Int,
    private val ocapiDatabase: OCAPIDatabase
) : RemoteMediator<Int, Story>() {

    private val storyService = StoryService.create()

    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, Story>
    ): MediatorResult {
        val page = when (loadType) {
            LoadType.REFRESH -> {
                val remoteKeys = getRemoteKeysClosestToCurrentPosition(state)
                remoteKeys?.nextKey?.minus(1) ?: STORY_STARTING_PAGE_INDEX
            }
            LoadType.PREPEND -> {
                val remoteKeys = getRemoteKeysForFirstItem(state) ?: 
                    throw InvalidObjectException("Remote key and the prevKey should not be null")

                remoteKeys.prevKey ?: return MediatorResult.Success(endOfPaginationReached = true)
            }
            LoadType.APPEND -> {
                val remoteKeys = getRemoteKeysForLastItem(state) ?:
                    throw InvalidObjectException("Remote key should not be null for $loadType")

                remoteKeys.nextKey ?: return MediatorResult.Success(endOfPaginationReached = true)
            }
        }

        try {
            val stories = storyService.getStories(page = page, category = category)

            val endOfPaginationReached = stories.isEmpty()

            ocapiDatabase.withTransaction {
                if (loadType == LoadType.REFRESH && stories.isNotEmpty()) {
                    ocapiDatabase.storyDao().deleteAll()
                    ocapiDatabase.storyRemoteKeysDao().deleteAll()
                }
 
                val prevKey = if (page == STORY_STARTING_PAGE_INDEX) null else page - 1
                val nextKey = if (endOfPaginationReached) null else page + 1

                val keys = stories.map {
                    StoryRemoteKeys(
                        storyId = it.storyId,
                        prevKey = prevKey,
                        nextKey = nextKey
                    )
                }
                ocapiDatabase.storyDao().insertAll(stories)
                ocapiDatabase.storyRemoteKeysDao().insertAll(keys)
            }

            return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
        } catch (exception: IOException) {
            return MediatorResult.Error(exception)
        } catch (exception: HttpException) {
            return MediatorResult.Error(exception)
        }
    }


    private suspend fun getRemoteKeysClosestToCurrentPosition(state: PagingState<Int, Story>): StoryRemoteKeys? {
        return state.anchorPosition?.let { position ->
            state.closestItemToPosition(position)?.storyId?.let { storyId ->
                ocapiDatabase.storyRemoteKeysDao().getRemoteKeysById(storyId)
            }
        }
    }

    private suspend fun getRemoteKeysForFirstItem(state: PagingState<Int, Story>): StoryRemoteKeys? {
        return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
            ?.let { story ->
                ocapiDatabase.storyRemoteKeysDao().getRemoteKeysById(story.storyId)
            }
    }

    private suspend fun getRemoteKeysForLastItem(state: PagingState<Int, Story>): StoryRemoteKeys? {
        return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
            ?.let { story ->
                ocapiDatabase.storyRemoteKeysDao().getRemoteKeysById(story.storyId)
            }
        }
    }
}

为了更清楚,这里是打印当前页码以从网络获取的日志。

2020-07-22 18:21:35.767 3510-3510/com.milan.ocapi I/STORY_PAGE: 2
2020-07-22 18:21:36.599 3510-3510/com.milan.ocapi I/STORY_PAGE: 2
2020-07-22 18:21:37.517 3510-3510/com.milan.ocapi I/STORY_PAGE: 2
2020-07-22 18:21:38.456 3510-3510/com.milan.ocapi I/STORY_PAGE: 2
2020-07-22 18:21:39.723 3510-3510/com.milan.ocapi I/STORY_PAGE: 2
2020-07-22 18:21:40.459 3510-3510/com.milan.ocapi I/STORY_PAGE: 2
2020-07-22 18:21:41.275 3510-3510/com.milan.ocapi I/STORY_PAGE: 2
2020-07-22 18:21:42.191 3510-3510/com.milan.ocapi I/STORY_PAGE: 2
2020-07-22 18:21:43.095 3510-3510/com.milan.ocapi I/STORY_PAGE: 2
2020-07-22 18:21:44.010 3510-3510/com.milan.ocapi I/STORY_PAGE: 2
2020-07-22 18:21:44.903 3510-3510/com.milan.ocapi I/STORY_PAGE: 2

如您所见,它不会获取下一个(即 3、4、5)页面。它进入循环。 为了测试,我还将@PrimaryKey 更改为链接(这是字符串类型的值),它也在这种情况下工作。

所以我很确定@PrimaryKey 长度有问题。

【问题讨论】:

    标签: android kotlin android-recyclerview android-room android-paging


    【解决方案1】:

    直接回答您的问题 - Paging 中键的字符长度没有这样的限制。

    您在RemoteMediator 中一遍又一遍地加载同一页面的原因是由于 Paging 无法告诉您已通过endOfPaginationReached 到达最后一页,但收到无效通知,告诉它数据已更新,因此它应该尝试再次加载。

    在加载第二页后检查stories.isEmpty() 是否为true,如果是,请尝试立即返回return MediatorResult.Success(endOfPaginationReached = true)

    此外,请检查PagingSource 是否以某种方式无效,尽管您正在获取同一页面。写入相同的项目是否会以某种方式触发失效或更新列,即使它在技术上是在同一页面上键入的,也许每次您提出请求时日期字段都会改变?

    【讨论】:

    • 好的。我会尝试检查stories.isEmpty()true 还是not。但是你能解释一下,为什么它与link(字符串类型)作为主键完美配合?两种情况下的数据相同。
    猜你喜欢
    • 2023-01-18
    • 2020-12-12
    • 2018-05-18
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多