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

Composable修改MutableState<Flow<List<Object>>>时陷入无限循环

问题原因分析

你的代码陷入无限重组循环的核心原因是:

  • 每次Composable重组时,都会执行viewModel.getCategoryLocations(useCategoryTag),而这个方法内部每次都会直接修改locations.value,触发ViewModel的State更新,进而导致Composable再次重组,形成循环。
  • getCategoryLocations的设计逻辑错误:它不应该在每次调用时强制更新Flow,而是应该由ViewModel内部管理筛选状态,仅在分类标签变化时才切换数据源。
修复方案

1. 重构ViewModel,用StateFlow管理状态

将原来的MutableState<Flow<List<Location>>>替换为StateFlow,让ViewModel内部自动处理分类标签变化后的数据源切换:

class HomeViewModel @Inject constructor(
    private val firestoreRepository: FirestoreRepository
) : ViewModel() {
    // 持有当前选中的分类标签,默认null
    private val _selectedCategoryTag = MutableStateFlow<CategoryTags?>(null)
    val selectedCategoryTag = _selectedCategoryTag.asStateFlow()

    // 根据选中的标签自动切换数据源,输出最终的Location列表StateFlow
    val locations: StateFlow<List<Location>> = _selectedCategoryTag
        .flatMapLatest { tag ->
            if (tag == null) {
                firestoreRepository.locations
            } else {
                firestoreRepository.getLocationCategory(tag, locationTags.toList())
            }
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000), // 优化订阅生命周期
            initialValue = emptyList()
        )

    // 对外暴露更新分类标签的方法
    fun updateSelectedCategoryTag(tag: CategoryTags?) {
        _selectedCategoryTag.value = tag
    }
}

2. 修改LocationScreen,仅观察状态而非主动触发更新

去掉每次重组都会调用的getCategoryLocations,改为直接观察ViewModel的locations状态,并用LaunchedEffect处理初始标签的设置:

fun LocationScreen(
    modifier: Modifier = Modifier,
    viewModel: HomeViewModel = hiltViewModel(),
    initialCategoryTag: CategoryTags? = null,
    onLocationClick: (String) -> Unit,
    onBackClick: () -> Unit = {}
) {
    // 仅在初始标签变化时,设置一次ViewModel的选中标签
    LaunchedEffect(initialCategoryTag) {
        viewModel.updateSelectedCategoryTag(initialCategoryTag)
    }

    // 直接观察ViewModel输出的最终Location列表
    val locations by viewModel.locations.collectAsStateWithLifecycle()

    // 按钮点击逻辑修改:仅更新ViewModel的选中标签
    // ... 你的按钮布局代码
    onClick = {
        val targetTag = if (initialCategoryTag == null) {
            item as CategoryTags
        } else {
            // 根据你的二次筛选需求调整,比如切换新标签或重置
            item as CategoryTags
        }
        viewModel.updateSelectedCategoryTag(targetTag)
    }
}

3. 额外优化点

  • 原数据层的getLocationCategory每次调用都会创建新的Flow,现在通过flatMapLatest可以自动取消之前的Flow订阅,避免不必要的Firestore连接和资源泄漏。
  • stateIn的SharingStarted.WhileSubscribed(5000)设置可以在Composable离开屏幕后延迟5秒取消订阅,平衡实时性和资源消耗。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 05:17:33