You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的实例化方式:

  1. ViewComponent被重复创建:View可组合项的component参数用了默认值ViewComponent(),每当listFromDatabase变化(比如点击Search),View重组时会创建新的ViewComponent,原有排序状态和列表状态全部丢失。
  2. 每次重组都会重置列表: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 01:10:57