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

Kotlin与Jetpack Compose实现变量真正不可变及计数问题解决

问题:固定Jetpack Compose中统计值直到主动更新

核心问题

需要统计数据库中不同分组的记录数,但当前通过切换sortType复用同一个groceries流,导致后续查询会覆盖之前的计数。希望每个分组的计数能固定下来,直到主动触发重新统计。

当前代码

ViewModel代码

private val _sortType = MutableStateFlow(SortType.CHECK_TO_ADD)
private val _groceries = _sortType
    .flatMapLatest { _sortType ->
        when (_sortType) {
            SortType.ITEM_NAME -> dao.listAllItems()
            SortType.CHECK_TO_ADD -> dao.listAllItemsChecked()
            SortType.ONLY_CHECK_TO_ADD -> dao.listShoppingList()
            SortType.ONLY_CHECKED_CHECKED -> dao.listPurchasedItems()
        }

    }

private val _state = MutableStateFlow(GroceriesState())
val state = combine(_state, _sortType, _groceries) {
                                                   state, sortType, groceries ->
    state.copy(
        groceries = groceries,
        sortType = sortType
    )
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), GroceriesState())

DAO代码

//List purchased items
@Query("SELECT * FROM grocery_table WHERE checkChecked = 1")
fun listPurchasedItems() : Flow<List<Groceries>>


//
//       QUERIES IN THE SORT ROUTINE
//
//List the items in shopping list - INCLUDED IN SORT
@Query("SELECT * FROM grocery_table WHERE checkToAdd = 1")
fun listShoppingList() : Flow<List<Groceries>>


//list all grocery items - INCLUDED IN SORT
@Query("SELECT * FROM grocery_table ORDER BY itemName ASC")
fun listAllItems(): Flow<List<Groceries>>


//list all grocery items to create grocery list and add new items to DB- INCLUDED IN SORT
@Query("SELECT * FROM grocery_table ORDER BY checkToAdd ASC, itemName ASC")
fun listAllItemsChecked(): Flow<List<Groceries>>

Composable中的问题代码

onEvent(GroceriesEvent.SortGroceries(SortType.ONLY_CHECKED_CHECKED))
val countPurchased: Int = state.groceries.size
onEvent(GroceriesEvent.SortGroceries(SortType.ONLY_CHECK_TO_ADD))
val countSList:Int = state.groceries.size
Text(
    text = "Shopping List",
    modifier = Modifier
        .weight(1f)
        .padding(start = 40.dp),
    fontSize = 17.sp,
    fontWeight = FontWeight.SemiBold,
    color = md_theme_dark_onTertiaryContainer
)
Text(
    text = "${countSList}/${countPurchased}",
    modifier = Modifier,
    fontSize = 17.sp,
    fontWeight = FontWeight.SemiBold,
    color = md_theme_dark_onTertiaryContainer
)

解决方案

1. 修改UIState,添加分组计数字段

扩展GroceriesState,专门存储各分组的计数,避免复用同一个列表:

data class GroceriesState(
    val groceries: List<Groceries> = emptyList(),
    val sortType: SortType = SortType.CHECK_TO_ADD,
    // 新增字段存储固定计数
    val purchasedCount: Int = 0,
    val shoppingListCount: Int = 0
)

2. ViewModel中同时监听多个查询流

不再通过切换sortType切换查询,而是同时监听所有需要统计的流,直接更新对应计数字段:

private val _sortType = MutableStateFlow(SortType.CHECK_TO_ADD)
// 单独监听各个查询流
private val allItems = dao.listAllItems()
private val allItemsChecked = dao.listAllItemsChecked()
private val purchasedItems = dao.listPurchasedItems()
private val shoppingListItems = dao.listShoppingList()

private val _state = MutableStateFlow(GroceriesState())
val state = combine(
    _state,
    _sortType,
    allItems,
    allItemsChecked,
    purchasedItems,
    shoppingListItems
) { state, sortType, allItems, allCheckedItems, purchasedItems, shoppingItems ->
    // 保留原有列表展示逻辑
    val currentGroceries = when(sortType) {
        SortType.ITEM_NAME -> allItems
        SortType.CHECK_TO_ADD -> allCheckedItems
        SortType.ONLY_CHECK_TO_ADD -> shoppingItems
        SortType.ONLY_CHECKED_CHECKED -> purchasedItems
    }
    state.copy(
        groceries = currentGroceries,
        sortType = sortType,
        // 固定各分组计数
        purchasedCount = purchasedItems.size,
        shoppingListCount = shoppingItems.size
    )
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), GroceriesState())

3. 更新Composable代码,直接读取固定计数

移除手动切换sortType的逻辑,直接从State中读取预存的固定值:

// 直接读取State中的固定计数
val countPurchased = state.purchasedCount
val countSList = state.shoppingListCount
Text(
    text = "Shopping List",
    modifier = Modifier
        .weight(1f)
        .padding(start = 40.dp),
    fontSize = 17.sp,
    fontWeight = FontWeight.SemiBold,
    color = md_theme_dark_onTertiaryContainer
)
Text(
    text = "${countSList}/${countPurchased}",
    modifier = Modifier,
    fontSize = 17.sp,
    fontWeight = FontWeight.SemiBold,
    color = md_theme_dark_onTertiaryContainer
)

原理说明

  • 通过ViewModel同时监听所有需要统计的数据库流,将每个分组的计数存储在UIState的独立字段中,这些字段会随数据库更新自动同步,但不会互相覆盖。
  • Composable直接读取State中的计数字段,无需手动切换查询,彻底避免了值被覆盖的问题。

内容的提问来源于stack exchange,提问作者Ray

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 03:12:32