Compose侧滑删除状态因重复ID保留旧数据是否会导致崩溃?
Jetpack Compose SwipeToDismiss 重复ID列表崩溃问题分析
问题场景
应用功能
- 加载包含2个项的新列表(项ID固定为0和1,每项带随机文本)
- 支持侧滑删除任意列表项
触发崩溃的操作步骤
- 首次加载新列表
- 侧滑删除第一个项(ID=0)
- 加载新列表(项ID仍为0和1,但文本为新的随机值)
- 侧滑删除第二个项(ID=1)→ 应用崩溃
崩溃原因
侧滑删除第二次加载的ID=1项时,实际操作的是第一次加载的ID=1项,导致调用viewModel.removeItem(item)时,传入的旧item对象不存在于新列表中,引发崩溃。
关键代码中的问题点:
val dismissState = rememberDismissState( confirmStateChange = { Log.d("Track", "$item\n${myListState.value.toMutableList()}") viewModel.removeItem(item) true } )
rememberDismissState会记住首次加载时捕获的旧item对象,即使列表刷新为新数据,只要项ID未变,Compose会复用之前的组件状态,导致回调里的item始终是旧数据。
完整的LazyColumn实现:
LazyColumn(modifier = Modifier.fillMaxHeight()) { items( items = myListState.value, key = { todoItem -> todoItem.id } ) { item -> val dismissState = rememberDismissState( confirmStateChange = { viewModel.removeItem(item) true } ) SwipeToDismiss( state = dismissState, background = { dismissState.dismissDirection ?: return@SwipeToDismiss Box(modifier = Modifier.fillMaxSize().background(Color.Red)) }, dismissContent = { // 列表项的行布局 } ) } }
问题解答
- 这是代码问题:你没有让
rememberDismissState感知到列表项的更新。当列表刷新后,ID相同的项被Compose视为同一个组件,复用了旧的dismissState,而该状态里的item还是旧数据。 - 不是Google框架Bug:SwipeToDismiss允许列表项ID重复,但需要确保组件状态与当前的列表项数据绑定。你改用
key = { todoItem -> todoItem.title }后正常,是因为每次新列表的title都是随机值,每个项的key唯一,Compose会创建新的组件和对应的dismissState,此时捕获的item是最新的。
解决方案
方案一:调整列表项的key,确保唯一标识当前实例
结合ID和内容生成唯一key,让Compose在数据更新时重新创建组件和状态:
items( items = myListState.value, key = { todoItem -> "${todoItem.id}-${todoItem.title.hashCode()}" } ) { item -> // 后续代码不变 }
方案二:让dismissState依赖当前item,数据更新时重建状态
通过remember包裹DismissState,指定依赖项为item,确保数据变化时重新创建状态:
val dismissState = remember(item) { DismissState( confirmStateChange = { viewModel.removeItem(item) true } ) }
内容的提问来源于stack exchange,提问作者Elye
相关产品推荐
相关产品推荐

