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

如何处理MVVM架构下StateFlow/SharedFlow观察者的冗余样板代码问题

MVVM 架构 Flow/LiveData 样板代码优化方案

纯ViewModel层面无法完全消除UI层的订阅逻辑,但可通过以下ViewModel侧的改造方案,配合少量UI层封装,大幅减少重复样板代码:

1. 聚合页面状态为单一UI State实例

不要为每个业务字段单独定义StateFlow,将页面所有需要渲染的状态聚合到一个数据类中,仅暴露一个StateFlow给UI层订阅:

// ViewModel 层定义统一页面状态
data class OrderDetailUiState(
    val order: Resource<Order>? = null,
    val deliverResult: Resource<Unit>? = null,
    val isLoading: Boolean = false,
    val errorMsg: String? = null
)

class OrderDetailViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(OrderDetailUiState())
    // 对外只暴露一个可读StateFlow
    val uiState: StateFlow<OrderDetailUiState> = _uiState.asStateFlow()

    fun loadOrderDetail(orderId: String) = viewModelScope.launch {
        _uiState.update { it.copy(isLoading = true) }
        orderRepo.getOrder(orderId)
            .onSuccess { order ->
                _uiState.update { it.copy(isLoading = false, order = Resource.Success(order)) }
            }
            .onFailure { err ->
                _uiState.update { it.copy(isLoading = false, errorMsg = err.message) }
            }
    }

    fun deliverOrder(orderId: String) = viewModelScope.launch {
        orderRepo.deliver(orderId)
            .onSuccess {
                _uiState.update { it.copy(deliverResult = Resource.Success(Unit)) }
            }
            .onFailure { err ->
                _uiState.update { it.copy(deliverResult = Resource.Error(err)) }
            }
    }
}

改造后UI层仅需要订阅一次uiState,不需要针对每个业务字段单独订阅。

2. 统一封装一次性事件分发

对于Toast、Snackbar、页面跳转这类一次性消费的事件,在ViewModel侧定义密封类事件,通过SharedFlow统一发射,避免UI层重复判断Resource状态:

// 定义页面一次性事件密封类
sealed class OrderDetailEvent {
    object ShowLoading: OrderDetailEvent()
    object HideLoading: OrderDetailEvent()
    data class ShowSnackbar(val msg: String): OrderDetailEvent()
    object NavigateBack: OrderDetailEvent()
}

class OrderDetailViewModel : ViewModel() {
    private val _event = MutableSharedFlow<OrderDetailEvent>(extraBufferCapacity = 1)
    val event: SharedFlow<OrderDetailEvent> = _event.asSharedFlow()

    fun deliverOrder(orderId: String) = viewModelScope.launch {
        _event.emit(OrderDetailEvent.ShowLoading)
        orderRepo.deliver(orderId)
            .onSuccess {
                _event.emit(OrderDetailEvent.HideLoading)
                _event.emit(OrderDetailEvent.NavigateBack)
            }
            .onFailure { err ->
                _event.emit(OrderDetailEvent.HideLoading)
                _event.emit(OrderDetailEvent.ShowSnackbar(err.message ?: "操作失败"))
            }
    }
}

UI层只需要订阅一次事件流,统一处理所有事件逻辑,无需每个Flow单独做状态判断。

3. 配合UI层扩展函数进一步精简代码

可在UI层封装通用的Resource收集扩展函数,和上述ViewModel改造方案搭配使用,彻底消除重复的when判断逻辑:

// Fragment 扩展函数封装Resource收集逻辑
fun <T> Fragment.collectResource(
    flow: Flow<Resource<T>>,
    onLoading: () -> Unit = { showLoading() },
    onError: (String?) -> Unit = { msg -> 
        showSnackbar(msg ?: "未知错误")
        hideLoading()
    },
    onSuccess: (T) -> Unit
) {
    lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            flow.collect { res ->
                when(res) {
                    is Resource.Loading -> onLoading()
                    is Resource.Error -> onError(res.message)
                    is Resource.Success -> onSuccess(res.data)
                }
            }
        }
    }
}

使用时仅需要传入成功回调即可,默认处理加载和错误逻辑:

collectResource(orderDetailVM.order) { order ->
    setUpViews(order)
}

优化效果

改造后原本N个Flow需要写N次when判断的样板代码完全消除,页面代码体量可减少70%以上,可读性和可维护性大幅提升。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 11:27:02