Jetpack Compose:LazyColumn排序后重组随即恢复原顺序问题排查
问题描述
我在Compose的View可组合项里用LazyColumn展示状态持有者ViewComponent中的列表,还有个Sort按钮切换升序/降序排序,原本功能正常。后来加了个Search按钮,点击后从数据库获取数据替换当前列表,数据替换成功,但排序功能出问题了:每次点Sort按钮,列表排序后立刻恢复原顺序。用println验证排序确实执行了,但之后马上重组把顺序重置了。想知道injectListFromDatabase是不是问题根源?
数据类定义
data class GetToolsWithService( val id: Int, ) data class ToolListState( val tools: List<GetToolsWithService>, )
View可组合项代码
@Composable fun View( component: ViewComponent = ViewComponent(), listFromDatabase: List<GetToolsWithService>, ) { if (listFromDatabase.isNotEmpty()) component.injectListFromDatabase(listFromDatabase) val stateList by component.stateList Button(onClick = { component.sort() }) { Text("Sort") } LazyColumn { items(stateList.tools) { Text("$it") } } }
状态持有者ViewComponent代码
class ViewComponent( private val sortTable: SortTable = SortTable(), initialListOfTools: List<GetToolsWithService> = List(10) { GetToolsWithService(it) }, ) { val stateList: MutableState<ToolListState> = mutableStateOf(ToolListState(initialListOfTools)) fun sort() { println("Sort Start") stateList.value = stateList.value.copy(tools = sortTable(stateList.value.tools)) stateList.value.tools.forEach { println(it) } println("Sort end") } fun injectListFromDatabase(list: List<GetToolsWithService>) { stateList.value = stateList.value.copy(tools = list) } }
排序逻辑SortTable代码
enum class SortOrder { ASCENDING, DESCENDING } class SortTable( private var sortingOrderData: SortOrder = SortOrder.ASCENDING, ) { operator fun invoke(list: List<GetToolsWithService>): List<GetToolsWithService> = if (sortingOrderData == SortOrder.DESCENDING) { sortingOrderData = SortOrder.ASCENDING list.sortedBy { it.id } } else { sortingOrderData = SortOrder.DESCENDING list.sortedByDescending { it.id } } }
模拟数据库数据传递代码
Column { var list: List<GetToolsWithService> by remember { mutableStateOf(emptyList()) } Button(onClick = { list = List(10) { GetToolsWithService(it + 100) } // "+100"方便区分数据 }) { Text("Search") } View( listFromDatabase = list, ) }
问题根源分析
问题不在injectListFromDatabase本身,而在View可组合项的重组逻辑和ViewComponent的实例化方式:
- ViewComponent被重复创建:
View可组合项的component参数用了默认值ViewComponent(),每当listFromDatabase变化(比如点击Search),View重组时会创建新的ViewComponent,原有排序状态和列表状态全部丢失。 - 每次重组都会重置列表:
if (listFromDatabase.isNotEmpty()) component.injectListFromDatabase(listFromDatabase)在每次View重组时都会执行。点击Sort按钮触发排序后,stateList变化会引发View重组,此时listFromDatabase非空,会再次调用注入方法把列表重置为数据库原始顺序,这就是排序后立刻复原的直接原因。
修复方案
步骤1:保持ViewComponent实例唯一性
在父组件用remember保存ViewComponent实例,避免每次重组创建新对象:
Column { var list: List<GetToolsWithService> by remember { mutableStateOf(emptyList()) } // 用remember保证实例在组件生命周期内唯一 val viewComponent = remember { ViewComponent() } Button(onClick = { list = List(10) { GetToolsWithService(it + 100) } }) { Text("Search") } View( component = viewComponent, listFromDatabase = list, ) }
步骤2:仅在数据变化时注入新列表
用LaunchedEffect监听listFromDatabase的变化,只在数据更新时执行注入操作:
@Composable fun View( component: ViewComponent, listFromDatabase: List<GetToolsWithService>, ) { // 仅当listFromDatabase变化时执行注入 LaunchedEffect(listFromDatabase) { if (listFromDatabase.isNotEmpty()) { component.injectListFromDatabase(listFromDatabase) } } val stateList by component.stateList Button(onClick = { component.sort() }) { Text("Sort") } LazyColumn { items(stateList.tools) { Text("$it") } } }
额外优化:分离原始列表与排序结果(可选)
在ViewComponent中保存原始列表和当前排序规则,注入新数据时自动应用现有排序,提升用户体验:
class ViewComponent( private val sortTable: SortTable = SortTable(), initialListOfTools: List<GetToolsWithService> = List(10) { GetToolsWithService(it) }, ) { // 保存原始数据源 private val originalTools = mutableStateListOf<GetToolsWithService>().apply { addAll(initialListOfTools) } val stateList: MutableState<ToolListState> = mutableStateOf(ToolListState(initialListOfTools)) fun injectListFromDatabase(list: List<GetToolsWithService>) { originalTools.clear() originalTools.addAll(list) // 注入新数据后自动应用当前排序规则 stateList.value = stateList.value.copy(tools = sortTable.invoke(originalTools, applyOrder = false)) } fun sort() { stateList.value = stateList.value.copy(tools = sortTable.invoke(originalTools)) } } // 修改SortTable,支持是否切换排序顺序 class SortTable( private var sortingOrderData: SortOrder = SortOrder.ASCENDING, ) { operator fun invoke(list: List<GetToolsWithService>, applyOrder: Boolean = true): List<GetToolsWithService> { val targetOrder = if (applyOrder) { val newOrder = if (sortingOrderData == SortOrder.DESCENDING) SortOrder.ASCENDING else SortOrder.DESCENDING sortingOrderData = newOrder newOrder } else { sortingOrderData } return if (targetOrder == SortOrder.ASCENDING) { list.sortedBy { it.id } } else { list.sortedByDescending { it.id } } } }
内容的提问来源于stack exchange,提问作者djtomahou
相关产品推荐
相关产品推荐

