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

是否应为_isDataLoadingError与_isLoading创建LiveData包装类?附实现方案

为ViewModel的加载/错误状态创建包装类是否更优?

当然值得这么做!把加载状态、错误状态和数据状态封装到一个统一的包装类里,不仅能精简ViewModel里重复的LiveData声明,还能从根源上避免状态不一致的问题——比如之前可能出现“加载中”和“加载错误”同时为true的矛盾场景,用包装类就能彻底杜绝这种情况。

为什么这么做更优?

  • 状态强一致性:通过密封类的排他性,同一时间只会有唯一的状态生效,不会出现逻辑矛盾。
  • 代码更简洁:原来分散的_isLoading、_isDataLoadingError、_posts三个LiveData可以合并成一个,减少冗余。
  • UI层逻辑更清晰:UI只需要监听一个状态流,用分支语句就能覆盖所有场景,不用同时处理多个LiveData的回调。

具体实现方式

1. 定义状态包装密封类

首先创建一个密封类UiState,用来封装所有可能的UI状态:

sealed class UiState<out T> {
    // 加载中状态
    object Loading : UiState<Nothing>()
    // 加载成功,携带数据
    data class Success<out T>(val data: T) : UiState<T>()
    // 加载失败,可选携带错误信息
    data class Error(val message: String? = null) : UiState<Nothing>()
}

2. 修改ViewModel代码

把原来的三个LiveData替换成UiState类型的LiveData,并在业务逻辑中更新对应的状态:

class MainViewModel(private val schedulerProvider: BaseSchedulerProvider) : BaseViewModel() {
    // 用单个UiState LiveData替代原来的三个分散状态
    private val _uiState = MutableLiveData<UiState<List<Post>>>()
    val uiState: LiveData<UiState<List<Post>>> get() = _uiState

    init {
        showPhotos()
    }

    fun showPhotos() {
        EspressoIdlingResource.increment()
        // 发起请求时,先发送加载中状态
        _uiState.postValue(UiState.Loading)
        compositeDisposable.add(Network.items.getPhotos()
            .subscribeOn(schedulerProvider.io())
            .observeOn(schedulerProvider.ui())
            .doFinally {
                if (!EspressoIdlingResource.countingIdlingResource.isIdleNow) {
                    EspressoIdlingResource.decrement()
                }
            }
            .subscribe({ networkPhotos ->
                // 获取图片成功后,继续获取帖子
                showPosts(networkPhotos)
            }) { error ->
                // 获取图片失败,发送错误状态
                _uiState.postValue(UiState.Error(error.localizedMessage))
                Timber.e(error)
            })
    }

    private fun showPosts(networkPhotos: List<NetworkPhoto>) {
        EspressoIdlingResource.increment()
        _uiState.postValue(UiState.Loading)
        compositeDisposable.add(Network.items.getPosts()
            .subscribeOn(schedulerProvider.io())
            .observeOn(schedulerProvider.ui())
            .doFinally {
                if (!EspressoIdlingResource.countingIdlingResource.isIdleNow) {
                    EspressoIdlingResource.decrement()
                }
            }
            .subscribe({ networkPosts ->
                // 转换为领域模型后,发送成功状态并携带数据
                val domainPosts = PostAndImages(networkPosts, networkPhotos).asDomaineModel()
                _uiState.postValue(UiState.Success(domainPosts))
            }) { error ->
                _uiState.postValue(UiState.Error(error.localizedMessage))
                Timber.e(error)
            })
    }
}

3. UI层的状态处理

在Activity/Fragment中,只需要监听uiState,用when语句处理不同状态即可:

viewModel.uiState.observe(viewLifecycleOwner) { state ->
    when (state) {
        is UiState.Loading -> {
            // 显示加载指示器
            loadingIndicator.visibility = View.VISIBLE
        }
        is UiState.Success -> {
            loadingIndicator.visibility = View.GONE
            // 展示加载成功的数据
            postAdapter.submitList(state.data)
        }
        is UiState.Error -> {
            loadingIndicator.visibility = View.GONE
            // 展示错误提示
            Toast.makeText(this, state.message ?: "加载失败", Toast.LENGTH_SHORT).show()
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:46:26