android - Room Table 的 @PrimaryKey 在 Android Paging 中产生意外行为
问题描述
我正在按照这个codelab教程Paging
使用数据库来实现缓存: - https://codelabs.developers.google.com/codelabs/android-paging/#13Room
但是,在实现缓存部分时,我在将下一页附加到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..."
}
},
{..},
{..}
]
故事.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
)
远程调解器.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 长度有问题。
解决方案
直接回答您的问题 - Paging 中键的字符长度没有这样的限制。
您一遍又一遍地加载同一页面的原因RemoteMediator
是由于 Paging 无法告诉您已经到达最后一页endOfPaginationReached
,而是收到无效通知,告诉它数据已更新,因此它应该尝试再次加载.
检查stories.isEmpty()
是true
在加载第二页之后,如果是,请尝试立即返回return MediatorResult.Success(endOfPaginationReached = true)
?
另外,请检查是否以PagingSource
某种方式无效,尽管您正在获取同一页面。编写相同的项目是否会以某种方式触发失效或更新列,即使它在技术上是在同一页面上键入的,也许每次您提出请求时日期字段都会改变?
推荐阅读
- css - 如何制作子导航栏两到三列
- angular - 为什么第一次渲染时我的组件中的对象对象闪烁,(Angular)
- flutter - Flutter Hot Reload 不适用于模拟器。如何删除开发工具?
- priority-web-sdk - 优先级 REST:我的 GET 请求得到 http 响应 200,但内部没有实际数据
- ansible - 使用 Ansible 分子测试 Acme.sh
- express - 无法将数据发布到 Google Cloud Run
- powershell - 有没有其他方法可以使用soap api从servicenow获取记录?
- python - SyntaxError:调用“打印”时缺少括号。你的意思是 print('Making request to: ' + url) 吗?无印刷
- javascript - 通过 webpack 导入时,引导工具提示不适用于 jquery.slim
- javascript - $.each 中的 setTimeout