简体   繁体   English

Room Table 的 @PrimaryKey 在 Android 分页中产生意外行为

[英]@PrimaryKey of Room Table creates an unexpected behaviour in Android Paging

I am implementing Paging with cache using Room database by following this codelab tutorial: - https://codelabs.developers.google.com/codelabs/android-paging/#13我正在按照这个codelab教程使用Room数据库使用缓存实现Paging : - https://codelabs.developers.google.com/codelabs/android-paging/#13

However while implementing the caching part, I have come across some strange behaviour while appending the next page in recyclerview .然而,在实现缓存部分时,我在recyclerview中附加下一页时遇到了一些奇怪的行为。

The RemoteMediator only fetches the 2nd page data in a continuous infinite loop. RemoteMediator仅在连续无限循环中获取第二页数据。 Rather than fetching data for the next page it continuously fetches data for the 2nd page.它不是为下一页获取数据,而是为第二页连续获取数据。

I have found out that this is happening because of @PrimaryKey of table.我发现这是由于表的@PrimaryKey 而发生的。 I have Int as @PrimaryKey in the table and it is 5 characters long.我在表中有Int作为@PrimaryKey,它有 5 个字符长。

The same is implemented in the codelab tutorial, where they also have Int as @PrimaryKey.在 codelab 教程中也实现了相同的功能,他们也将Int作为@PrimaryKey。 But they have a minimum 7 character long randomized primary key.但它们至少有 7 个字符长的随机主键。 And it works perfectly fine.它工作得很好。 To verify this, I have made my @PrimaryKey around 15 character long by adding为了验证这一点,我通过添加使我的 @PrimaryKey 长约 15 个字符

id = id + System.currentTimeMillis()

and after doing this it works perfectly.并且在这样做之后它完美地工作。 But in real scenario I can't modify the id.但在实际情况下,我无法修改 id。

So, is there any limitations in @PrimayKey of Room table(ie like the primary key must have minimum 7 character)?那么,Room 表的@PrimayKey 是否有任何限制(即主键必须至少有 7 个字符)? Or is @PrimaryKey have to be randomize in Room?还是 @PrimaryKey 必须在 Room 中随机化? Or I am doing something wrong here?或者我在这里做错了什么?

Here is the code and JSON.这是代码和 JSON。

JSON FORMAT 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故事.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远程调解器.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)
            }
        }
    }
}

To make it more clear, here is the log which prints the the current page number to fetch from network.为了更清楚,这里是打印当前页码以从网络获取的日志。

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

As you can see it does not fetch next(ie 3, 4, 5) page.如您所见,它不会获取下一个(即 3、4、5)页面。 It goes into loop.它进入循环。 For testing, I have also changed @PrimaryKey to link(which is String type value), and it is working in that condition also.为了测试,我还将@PrimaryKey 更改为链接(这是字符串类型值),它也在这种情况下工作。

So I am pretty sure that there is something wrong with @PrimaryKey length.所以我很确定@PrimaryKey 长度有问题。

To answer your question directly - there is no such limitation for character length of keys in Paging.直接回答您的问题 - Paging 中键的字符长度没有这样的限制。

The reason that you are loading the same page over and over again in RemoteMediator is due to Paging being unable to tell you've hit the last page through endOfPaginationReached , but is receiving invalidations telling it the data has updates, and so it should try to load again.您在RemoteMediator中一遍又一遍地加载同一页面的原因是由于 Paging 无法通过endOfPaginationReached告诉您已经到达最后一页,但是收到无效通知,告诉它数据已更新,因此它应该尝试再次加载。

Check that stories.isEmpty() is true after loading the second page and if so, try returning immediately with return MediatorResult.Success(endOfPaginationReached = true) ?在加载第二页后检查stories.isEmpty()是否为true ,如果是,请尝试立即return MediatorResult.Success(endOfPaginationReached = true)

Also, check to see if PagingSource is being invalidated somehow although you're fetching the same page.此外,请检查PagingSource是否以某种方式无效,尽管您正在获取同一页面。 Does writing the same items somehow trigger invalidation or update a column even though it's technically keyed on the same page, perhaps the date field is changing each time you make the request?编写相同的项目是否会以某种方式触发失效或更新列,即使它在技术上是在同一页面上键入的,也许每次您提出请求时日期字段都会改变?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM