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

如何在Jetpack Compose的MVVM架构中正确显示Firebase操作进度指示器?

问题描述

在MVVM架构结合Jetpack Compose的项目中,向Firebase添加条目时,希望在操作开始时显示进度指示器,完成后自动隐藏。现有实现中使用了额外的addingItem状态来控制进度条显示,但感觉这不是正确的实现方式,想寻求更合理的方案。

现有相关代码如下:

仓库层接口与实现

接口定义:

suspend fun addItem(item: Item): Response<String>

实现代码:

override suspend fun addItem(item: Item) = try {
    val itemId = itemsRef.add(item).await().id
    Response.Success(itemId)
} catch (ex: Exception) {
    Response.Failure(ex)
}

现有ViewModel代码

class ItemViewModel @Inject constructor(
    private val repo: ItemRepository
): ViewModel() {
    var addItemResponse by mutableStateOf<Response<String>>(Response.Loading)
        private set

    fun addItem(item: Item) = viewModelScope.launch {
        addItemResponse = repo.addItem(item)
    }
}

现有UI代码

var addingItem by remember { mutableStateOf(false) }

Button(
    onClick = {
        viewModel.addItem(Item("New Item"))
        addingItem = true
    }
) {
    Text(text = "Add Item")
}

if (addingItem) {
    when(val addItemResponse = viewModel.addItemResponse) {
        is Response.Loading -> CircularProgressIndicator()
        is Response.Success -> addingItem = false
        is Response.Failure -> print(addItemResponse.ex)
    }
}
优化方案

核心思路是让ViewModel完全管理操作的状态生命周期,不需要UI层额外维护addingItem状态,直接通过ViewModel中的addItemResponse状态控制UI展示。

1. 扩展状态密封类并修复ViewModel

原Response类缺少“未开始”的状态,导致初始值设为Loading会让页面一开始就显示进度条。先扩展密封类:

sealed class Response<out T> {
    object Idle : Response<Nothing>()
    object Loading : Response<Nothing>()
    data class Success<out T>(val data: T) : Response<T>()
    data class Failure(val ex: Exception) : Response<Nothing>()
}

修改ViewModel,让状态流转更清晰:

class ItemViewModel @Inject constructor(
    private val repo: ItemRepository
): ViewModel() {
    var addItemResponse by mutableStateOf<Response<String>>(Response.Idle)
        private set

    fun addItem(item: Item) = viewModelScope.launch {
        addItemResponse = Response.Loading // 操作开始时标记为加载中
        addItemResponse = repo.addItem(item) // 操作完成后更新为成功/失败状态
    }

    // 可选:重置状态,允许用户再次执行添加操作
    fun resetAddItemState() {
        addItemResponse = Response.Idle
    }
}

2. 简化UI层实现

UI层直接观察ViewModel的状态,无需额外维护addingItem变量:

Button(
    onClick = { viewModel.addItem(Item("New Item")) },
    // 操作进行中禁用按钮,防止重复提交
    enabled = viewModel.addItemResponse !is Response.Loading
) {
    Text(text = "Add Item")
}

// 根据ViewModel状态渲染UI
when(val response = viewModel.addItemResponse) {
    is Response.Loading -> CircularProgressIndicator()
    is Response.Success -> {
        // 处理成功逻辑,比如显示提示
        LaunchedEffect(response) {
            println("条目添加成功,ID:${response.data}")
            viewModel.resetAddItemState() // 重置状态,允许再次操作
        }
    }
    is Response.Failure -> {
        // 处理错误逻辑
        LaunchedEffect(response) {
            println("条目添加失败:${response.ex.message}")
            viewModel.resetAddItemState() // 重置状态
        }
    }
    // 初始Idle状态不渲染任何内容
    is Response.Idle -> Unit
}

方案优势

  • 单一数据源:所有操作状态由ViewModel统一管理,避免UI层和ViewModel状态不一致的问题。
  • 状态流转清晰:从Idle→Loading→Success/Failure,状态变化完全由业务逻辑驱动,逻辑更可控。
  • 代码简洁:去掉冗余的addingItem变量,UI层只负责根据状态渲染,职责更单一。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 10:33:19