如何处理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
相关产品推荐
相关产品推荐

