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

Jetpack Compose离线应用中Kotlin Flow网络错误处理问题

问题描述

我正尝试基于Jetpack Compose开发一款离线优先应用,已创建仓库暴露本地数据流getCurrenciesStream(),以及用于调用API刷新数据并更新本地数据的refreshCurrencies()函数。但不知如何处理网络请求可能产生的错误:希望当.map{}中的refreshCurrencies()调用失败时,将currenciesUiState设置为CurrenciesUiState.Error。

当前ViewModel代码:

@HiltViewModel
class CurrenciesViewModel @Inject constructor(
    private val currenciesRepository: CurrenciesRepository,
): ViewModel() {
    val currenciesUiState = currenciesRepository.getCurrenciesStream()
        .map { currencies ->
            if (currencies.isNotEmpty()) {
                return@map CurrenciesUiState.Success(currencies)
            }
            refreshCurrencies()
            CurrenciesUiState.Loading
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000L),
            initialValue = CurrenciesUiState.Loading,
        )

    fun refreshCurrencies() {
        viewModelScope.launch {
            currenciesRepository.refreshCurrencies()
        }
    }
}

我曾尝试如下写法,但由于refreshCurrencies()启动协程后立即返回,无法捕获错误:

try {
    refreshCurrencies()
    CurrenciesUiState.Loading
} catch (e: IOException) {
    CurrenciesUiState.Error
}

另一种方案是在refreshCurrencies()内部添加try-catch,但这样就无法更新状态了?我是不是遗漏了什么?我知道用MutableStateFlow可以轻松实现,但想了解当前方案是否可行。

解决方案

方案一:基于原有Flow链式调用改造(无需MutableStateFlow)

你当前的问题核心是.map{}是同步操作,而refreshCurrencies()是异步启动协程,导致无法捕获异常。可以用transform操作符替换map,在Flow的协程上下文内执行刷新逻辑,从而捕获异常并发射错误状态:

@HiltViewModel
class CurrenciesViewModel @Inject constructor(
    private val currenciesRepository: CurrenciesRepository,
): ViewModel() {
    val currenciesUiState = currenciesRepository.getCurrenciesStream()
        .transform { currencies ->
            if (currencies.isNotEmpty()) {
                emit(CurrenciesUiState.Success(currencies))
            } else {
                emit(CurrenciesUiState.Loading)
                // 在Flow的协程上下文内执行刷新,可直接捕获异常
                try {
                    currenciesRepository.refreshCurrencies()
                    // 刷新成功后,本地数据更新会触发getCurrenciesStream()发射新数据,自动切换到Success状态
                } catch (e: IOException) {
                    emit(CurrenciesUiState.Error(e.message ?: "网络请求失败"))
                }
            }
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000L),
            initialValue = CurrenciesUiState.Loading,
        )

    fun refreshCurrencies() {
        viewModelScope.launch {
            try {
                currenciesRepository.refreshCurrencies()
            } catch (e: IOException) {
                // 若要处理手动刷新的错误,此方案无法直接更新stateIn生成的StateFlow,需结合方案二
            }
        }
    }
}

说明

  • transform允许在Flow的协程环境中执行异步操作,因此能直接捕获refreshCurrencies()的异常。
  • 刷新成功后,本地仓库数据更新会自动触发getCurrenciesStream()发射新数据,Flow会再次执行transform逻辑并发射Success状态。

方案二:使用MutableStateFlow(更灵活的通用方案)

如果需要支持手动刷新的错误处理,stateIn生成的只读StateFlow无法直接发射新状态,此时MutableStateFlow是更合适的选择,能统一处理初始化和手动刷新的错误:

@HiltViewModel
class CurrenciesViewModel @Inject constructor(
    private val currenciesRepository: CurrenciesRepository,
): ViewModel() {
    private val _currenciesUiState = MutableStateFlow<CurrenciesUiState>(CurrenciesUiState.Loading)
    val currenciesUiState: StateFlow<CurrenciesUiState> = _currenciesUiState.asStateFlow()

    init {
        viewModelScope.launch {
            // 监听本地数据变化,自动更新状态
            currenciesRepository.getCurrenciesStream().collect { currencies ->
                if (currencies.isNotEmpty()) {
                    _currenciesUiState.value = CurrenciesUiState.Success(currencies)
                } else {
                    // 本地无数据时自动触发刷新
                    refreshCurrencies()
                }
            }
        }
    }

    fun refreshCurrencies() {
        viewModelScope.launch {
            _currenciesUiState.value = CurrenciesUiState.Loading
            try {
                currenciesRepository.refreshCurrencies()
                // 刷新成功后,collect会收到新数据,自动切换到Success状态
            } catch (e: IOException) {
                _currenciesUiState.value = CurrenciesUiState.Error(e.message ?: "网络请求失败")
            }
        }
    }
}

说明

  • 通过MutableStateFlow持有UI状态,可在任意协程中直接更新状态,包括手动刷新时的错误处理。
  • 初始化时监听本地数据流,自动同步状态,本地无数据时触发刷新,符合离线优先的需求。

总结

  • 若仅需处理初始化时自动刷新的错误,方案一的Flow链式改造可行,无需引入MutableStateFlow。
  • 若需要支持手动刷新的错误反馈,方案二的MutableStateFlow是更直观、灵活的选择,也是Jetpack Compose中处理UI状态的常规模式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:39:53