使用Paging3从Room DB加载数据时重复数据引发Key重复崩溃
Paging3 + Room 重复数据导致LazyColumn「Key已被使用」崩溃问题解决
问题场景
使用Paging3从Room数据库加载聊天消息时,出现数据重复,进而触发LazyColumn崩溃,报错信息提示Key was already used。
相关代码
Room查询代码
@Query("SELECT * FROM Message WHERE roomId = :roomId ORDER BY cd DESC limit :pageSize") suspend fun getAllMessage(roomId: Long, pageSize: Int): List<Message>
MessagePagingSource实现
class MessagePagingSource( private val chatWindowRepository: ChatWindowRepository, private val roomId: Long ) : PagingSource<Int, Message>() { override fun getRefreshKey(state: PagingState<Int, Message>): Int? { return state.anchorPosition?.let { anchorPosition -> state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) } } override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Message> { return try { val pageNumber = params.key ?: 0 val pageSize = params.loadSize val response = chatWindowRepository.getMessages(roomId, pageSize) LoadResult.Page( data = response, prevKey = if (pageNumber > 0) pageNumber - 1 else null, nextKey = if (response.isNotEmpty()) pageNumber + 1 else null ) } catch (e: Exception) { Log.e("itemSnapshotList", "Error loading data: ${e.message}", e) LoadResult.Error(e) } } }
ViewModel代码
val messages = Pager( config = PagingConfig(pageSize = 10, prefetchDistance = 10), pagingSourceFactory = { MessagePagingSource(chatWindowRepository, roomId = room.roomId) } ).flow.cachedIn(viewModelScope)
UI代码
val list = vm.messages.collectAsLazyPagingItems() Log.d("itemSnapshotList", "list size is:${list.itemCount}") LazyColumn( modifier = Modifier .padding(it) .fillMaxSize(), reverseLayout = true ) { items( count = list.itemCount, key = list.itemKey { message -> message.uuid }, contentType = list.itemContentType { it.messageType }, itemContent = {index: Int -> val message = list[index] Log.d("itemSnapshotList", "uuid is : ${message?.uuid}") } ) }
崩溃日志
FATAL EXCEPTION: main Process: com.ainsoft.scoopmessenger, PID: 25528 java.lang.IllegalArgumentException: Key "1-120+1=1697018154509" was already used. If you are using LazyColumn/Row please make sure you provide a unique key for each item. at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:433) at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope.subcompose(SubcomposeLayout.kt:807) at androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScopeImpl.measure-0kLqBqw(LazyLayoutMeasureScope.kt:121) at androidx.compose.foundation.lazy.LazyListMeasuredItemProvider.getAndMeasure(LazyListMeasuredItemProvider.kt:48) at androidx.compose.foundation.lazy.LazyListMeasureKt.measureLazyList-50ZNEM8(LazyListMeasure.kt:181) at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke-0kLqBqw(LazyList.kt:322) at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke(LazyList.kt:187) at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$3$2$1.invoke-0kLqBqw(LazyLayout.kt:103) at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$3$2$1.invoke(LazyLayout.kt:96) at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(SubcomposeLayout.kt:644) at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126) at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:646) at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116) at androidx.compose.foundation.layout.FillNode.measure-3p2s80s(Size.kt:699) at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116) at androidx.compose.foundation.layout.PaddingValuesModifier.measure-3p2s80s(Padding.kt:455) at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116) at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1560) at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1556) at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2303) at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:473) at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:230) at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133) at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:113) at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1556) at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:35) at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:582) at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release(LayoutNode.kt:1147) at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release$default(LayoutNode.kt:1138) at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:331) at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:466) at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtree(MeasureAndLayoutDelegate.kt:546) at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtree(MeasureAndLayoutDelegate.kt:554) at androidx.compose.ui.platform.AndroidComposeView.forceMeasureTheSubtree(AndroidComposeView.android.kt:930) at androidx.compose.ui.node.Owner.forceMeasureTheSubtree$default(Owner.kt:239) 2023-10-11 15:10:23.929 25528-25528 AndroidRuntime com.ainsoft.scoopmessenger E at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:594)
问题原因
- Room查询未实现真正分页:当前查询仅通过
limit :pageSize限制返回条数,但缺少offset偏移参数,导致每次加载请求都会返回最新的pageSize条数据,Paging3的列表中出现大量重复的Message项。 - PagingSource加载逻辑错误:未根据页码计算数据偏移量,每次加载都请求相同的数据集,分页完全失效,最终导致LazyColumn中出现重复的UUID(作为item的key),触发「Key已被使用」的崩溃。
解决方案
1. 修改Room查询语句,添加分页偏移
更新DAO中的查询方法,增加offset参数,实现基于页码的分页:
@Query("SELECT * FROM Message WHERE roomId = :roomId ORDER BY cd DESC LIMIT :pageSize OFFSET :offset") suspend fun getAllMessage(roomId: Long, pageSize: Int, offset: Int): List<Message>
2. 调整MessagePagingSource的加载逻辑
在load方法中根据当前页码计算偏移量,确保每次加载不同的分页数据:
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Message> { return try { val pageNumber = params.key ?: 0 val pageSize = params.loadSize // 根据页码计算偏移量:当前页码 * 每页条数 val offset = pageNumber * pageSize val response = chatWindowRepository.getMessages(roomId, pageSize, offset) LoadResult.Page( data = response, prevKey = if (pageNumber > 0) pageNumber - 1 else null, nextKey = if (response.isNotEmpty()) pageNumber + 1 else null ) } catch (e: Exception) { Log.e("itemSnapshotList", "Error loading data: ${e.message}", e) LoadResult.Error(e) } }
3. 可选:验证UUID唯一性(额外保障)
确认Message实体中的uuid字段确实是全局唯一的,避免因UUID本身重复导致的key冲突。可以在插入数据时确保UUID生成逻辑正确,比如使用UUID.randomUUID().toString()生成。
内容的提问来源于stack exchange,提问作者Ain Soft
相关产品推荐
相关产品推荐

