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

在ViewModel架构的KMM应用中使用collectAsState及代码优化咨询

问题分析与优化方案

一、collectAsState的正确使用

UI层

你的写法可以简化,collectAsState返回的是State<T>对象,直接获取其value即可使用,无需用apply包裹:

val userState = usersViewModel.userInfo.collectAsState(initial = null)
// 使用 userState.value 访问用户数据

如果是在Composable函数中,这种写法会自动跟随Compose生命周期收集流,避免内存泄漏。

ViewModel层

建议将userInfo转换为StateFlow,确保配置变更时数据不丢失,同时更符合ViewModel的状态管理规范:

val userInfo = repo.getUserProfile()
    .catch { println("获取用户信息失败: $it") }
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000), // 5秒无订阅则停止收集,节省资源
        initialValue = null
    )

通过stateIn将冷流转为热流,WhileSubscribed策略能避免后台不必要的流收集。

Repository层

getUserProfile的写法冗余,Firestore的snapshots本身就是Flow,无需手动嵌套flow和collect,直接转换即可:

fun getUserProfile(): Flow<User?> = firestore.collection("User")
    .document(auth.currentUser?.uid ?: return flowOf(null)) // 避免!!空指针风险
    .snapshots
    .map { it.data<UserInfo>() }
    .catch {
        println("获取用户快照失败: $it")
        emit(null) // 发射null避免流终止
    }

同时用安全调用处理currentUser,消除空指针隐患。

二、getAllRestaurants的写法问题

当前实现存在两个核心问题:

  1. val user = getUserProfile()拿到的是Flow,在map中调用user.first()!!会每次餐厅快照更新时都重新挂起获取用户数据,重复请求且阻塞流处理,这是性能慢的主要原因之一。
  2. !!会引发空指针崩溃,如果用户数据未加载完成或获取失败,直接导致程序异常。

正确的写法是使用combine合并用户流和餐厅流,在流的组合中完成过滤:

fun getAllRestaurants(): Flow<List<Restaurant>> = combine(
    getUserProfile(),
    firestore.collection("Restaurant").snapshots
) { user, querySnapshot ->
    val userLocation = user?.userLocation ?: return@combine emptyList()
    querySnapshot.documents.mapNotNull { it.data<Restaurant>() } // 过滤解析失败的餐厅
        .filter { restaurant ->
            checkIfPointInsidePolygon(userLocation, restaurant.polygonList)
        }
}.catch {
    println("获取餐厅列表失败: $it")
    emit(emptyList())
}
  • combine会在任意一个流更新时触发计算,确保用户数据和餐厅数据同步。
  • mapNotNull过滤无效的餐厅数据,避免后续处理null。
  • 安全获取userLocation,用户数据未加载时返回空列表而非崩溃。

三、性能慢的排查方向

  1. 调试环境影响:调试模式下KMM会有额外的日志、断点检查,性能会比Release模式差,建议打包Release版本测试真实性能。
  2. Firestore数据量:如果餐厅数据量过大,每次快照更新都要遍历所有文档并执行checkIfPointInsidePolygon计算,会导致卡顿。可以考虑:
    • 在Firestore端添加地理位置过滤,减少客户端需要处理的数据量。
    • 缓存多边形判断的计算结果,避免重复执行。
  3. 流收集效率:确保ViewModel中使用stateIn的SharingStarted.WhileSubscribed策略,避免后台不必要的流收集,减少资源消耗。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 20:55:53