携带状态的LiveData

本文围绕Android MVVM架构中LiveData展开。LiveData是通知UI更新的核心组件,常链接异步操作结果。但异步操作有进度和状态,原处理方式零碎难维护。为此对LiveData扩展,增加代表异步操作状态的字段,且该状态可监听。还推荐了内置StateLiveData的AndroidKTX类库。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在Android MVVM架构中,LiveData作为通知UI更新的桥梁,地位极其重要,可以说是MVVM的核心组件。

在具体实践中,它往往链接着对数据的异步操作结果。比如在登录操作中,需要执行异步登录逻辑,逻辑完成的结果会得到用户信息数据,这个数据可能会赋值给LiveData,用代码表示如下:

class UserVM : ViewModel() {
    val userData = MutableLiveData<User>()
    
    fun login(){
        viewModelScope.launch { 
            val result = "http://www.lixiaojun.xin/api/login".http().post<User>().await()
            userData.postValue(result)
        }
    }
}
复制代码

这样的代码会大量出现在我们的VM层中。

问题

然而异步操作不是立即的,而且有进度,有状态的。我们的UI很可能需要知道当前的异步数据操作是否正在进行(可以显示进度条),是否已经完成,或者是否失败。

一般我们可以能这样做:

userVM.userData.observe(this, Observer{ 
    if(it==null){
        showFail() //显示请求失败
    }else{
        updateUI() //更新UI
    }
})
showProgress() //登录之前显示进度条
userVM.login() 

复制代码

虽然我们也能在代码的某些地方去插入状态展示,但这样的写法太过零碎,不易维护和管理。假设UI代码有几百行,你就会很难找到某个请求的进度条在哪里写着。

如果每个LiveData能携带自己的状态,我们就可以面向LiveData来进行状态更新,而且能在一个地方集中管理状态,这样就优雅的很。

实现

于是我们可以对LiveData进行扩展,增加一个state字段,代表当前异步操作的状态。由于状态应当是可监听的,所以state也是一个LiveData。代码如下:

/**
 * Description: 携带状态的LiveData
 * Create by lxj, at 2019/3/6
 */
class StateLiveData<T> : MutableLiveData<T>() {

    enum class State {
        Idle, Loading, Success,Error
    }
    val state = MutableLiveData<State>()

    init {
        clearState()
    }

    fun postValueAndSuccess(value: T) {
        super.postValue(value)
        postSuccess()
    }

    fun clearState() {
        state.postValue(State.Idle)
    }

    fun postLoading() {
        state.postValue(State.Loading)
    }

    fun postSuccess() {
        state.postValue(State.Success)
    }

    fun postError() {
        state.postValue(State.Error)
    }

    fun changeState(s: State) {
        state.postValue(s)
    }
}
复制代码

我们使用StateLiveData改写VM层的代码:

class UserVM : ViewModel() {
    val userData = StateLiveData<User>()
    fun login(){
        viewModelScope.launch {
            userData.postLoading()
            val result = "http://www.lixiaojun.xin/api/login".http().post<User>().await()
            if(result==null){
                userData.postError()   
            }else{
                userData.postValueAndSuccess(result)
            }
        }
    }
}
复制代码

而此时UI层对状态的监听变成了这样:

//统一管理LiveData的状态
userVM.userData.state.observe(this, Observer{ 
    when(it){
        StateLiveData.State.Loading -> showProgress()
        StateLiveData.State.Error -> showFail()
        //...其他状态处理
    }
})
userVM.userData.observe(this, Observer{ 
    updateUI() //直接更新UI
})
userVM.login() 
复制代码

推荐

上面的StateLiveData被内置在我的AndroidKTX类库中:github.com/li-xiaojun/… ,如果你用Kotlin开发Android,这个库将能够大大提高你的开发速度。我是俊哥,致力于推进现代化的Android开发,用最佳的实践,最优雅的代码教你最快速的开发高质量Android应用。

package com.st.base.ui; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.view.Window; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; import androidx.databinding.ViewDataBinding; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; public abstract class BaseActivity<V extends ViewDataBinding> extends AppCompatActivity implements IBaseView { protected V binding; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //页面接受的参数方法 initParam(); //隐藏顶部和底部栏 transparentNavBar(getWindow()); //私有的初始化Databinding和ViewModel方法 initViewDataBinding(savedInstanceState); //初始化ui initView(); //私有的ViewModel与View的契约事件回调逻辑 registorUIChangeLiveDataCallBack(); //页面数据初始化方法 initData(); //页面事件监听的方法,一般用于ViewModel层转到View层的事件注册 initViewObservable(); //页面监听回调 initCallback(); } protected abstract void initView(); protected abstract void initCallback(); protected abstract void releaseCallback(); private void initViewDataBinding(Bundle savedInstanceState) { //DataBindingUtil类需要在project的build中配置 dataBinding {enabled true }, 同步后会自动关联android.databinding包 binding = DataBindingUtil.setContentView(this, initContentView(savedInstanceState)); //支持LiveData绑定xml,数据改变,UI自动会更新 binding.setLifecycleOwner(this); initViewDataModel(); } protected void initViewDataModel() { } /** * 初始化根布局 * * @return 布局layout的id */ public abstract int initContentView(Bundle savedInstanceState); protected int initVariableId() { return 0; } protected void registorUIChangeLiveDataCallBack() { } @Override public void initParam() { } @Override public ViewModel initViewModel() { return null; } @Override public void initData() { } @Override public void initViewObservable() { } /** * 跳转页面 * * @param clz 所跳转的目的Activity类 * @param bundle 跳转所携带的信息 */ public void startActivity(Class<?> clz, Bundle bundle) { Intent intent = new Intent(this, clz); if (bundle != null) { intent.putExtras(bundle); } startActivity(intent); } /** * 创建ViewModel * * @param cls * @param <T> * @return */ public <T extends ViewModel> T createViewModel(FragmentActivity activity, Class<T> cls) { return new ViewModelProvider(activity).get(cls); } @Override protected void onDestroy() { super.onDestroy(); if (binding != null) { binding.unbind(); } releaseCallback(); } protected void transparentNavBar(@NonNull final Window window) { window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); //状态栏、导航栏透明 window.setStatusBarColor(Color.TRANSPARENT); window.setNavigationBarContrastEnforced(false); window.setNavigationBarColor(Color.TRANSPARENT); View decorView = window.getDecorView(); int vis = decorView.getSystemUiVisibility(); int option = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; decorView.setSystemUiVisibility(vis | option); } } --------- 这个代码的单元测试可以通过PowerMock和mockito实现吗
最新发布
07-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值