一篇文章了解Android MVVM开发 ----- ViewModel

前言

MVVM的架构模式,在前端使用React同学,或者在用Flutter开发的同学,都能体会到React的思想用数据直接渲染UI的方便。在Android引入jetpack组件后,如果你还在使用MVP或者MVC的架构方式,赶紧来使用下吧,绝对会让你说出两个字 “真香”。

这篇文章主要是介绍如何在项目中使用databinding + ViewModel +LiveData,体会下Android Jetpack给我们带来的方便。
实现了单个业务使用ViewModel以及多个Fragment中通信使用viewModel

知识点大纲

在这里插入图片描述

使用MVVM

环境配置

请在应用模块的 build.gradle 文件中添加 dataBinding 元素

android {
        ...
        dataBinding {
            enabled = true
        }
    }

依赖最新的jetpack家族,可以按需依赖, 要是你的项目里的ViewModel和LiveData还不是最新的话 建议升级到最新版本

    dependencies {
        def lifecycle_version = "2.2.0"
        def arch_version = "2.1.0"

        // ViewModel
        implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
        // LiveData
        implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
        // Lifecycles only (without ViewModel or LiveData)
        implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"

        // Saved state module for ViewModel
        implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"

        // Annotation processor
        annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
        // alternately - if using Java8, use the following instead of lifecycle-compiler
        implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

        // optional - helpers for implementing LifecycleOwner in a Service
        implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version"

        // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
        implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"

        // optional - ReactiveStreams support for LiveData
        implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version"

        // optional - Test helpers for LiveData
        testImplementation "androidx.arch.core:core-testing:$arch_version"
    }
    

实战部分

在这里我们模仿实现一下异步请求数据刷新我们的UI界面和同步修改页面。

图片名称

图片名称

databinding 数据视图绑定,布局修改

这里我们需要定义两个变量,一个是我们试图所对应的对象以及设置相关的点击事件

activity_view_model.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="click"
            type="android.view.View.OnClickListener" />


    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_async_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:text="异步修改数据,3秒后显示"/>

        <TextView
            android:id="@+id/tv_name"
            android:textSize="20sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="点击按钮,同步修改数据"/>

        <Button
            android:id="@+id/btn_click"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="点击修改数据"
            android:onClick="@{click::onClick}"/>

    </LinearLayout>
</layout>

UserBean 对象

public class UserBean {
    private String firstName;
    private String lastName;

    public UserBean(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

对应的ViewModel

这里我们用一个耗时操作来模仿网络请求, 定时操作所在的是工作线程,这里需要使用 postValue

public class UserViewModel extends ViewModel {
    private MutableLiveData<UserBean> users;
    private Disposable mTimeLimitDisposable;
    protected BaseSchedulerProvider mSchedulerProvider;
    private MutableLiveData<String> currentName; //定义String变量

    public MutableLiveData<String> getCurrentName() {
        if (currentName == null) {
            currentName = new MutableLiveData<String>();
        }
        return currentName;
    }

    public UserViewModel() {
        mSchedulerProvider = new BaseSchedulerProvider() {
            @NonNull
            @Override
            public Scheduler computation() {
                return Schedulers.trampoline();
            }

            @NonNull
            @Override
            public Scheduler io() {
                return Schedulers.trampoline();
            }

            @NonNull
            @Override
            public Scheduler ui() {
                return Schedulers.trampoline();
            }
        };
    }

    public LiveData<UserBean> getUsers() {
        if (users == null) {
            users = new MutableLiveData<UserBean>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
        //通过延时操作模仿网络请求
        if (mTimeLimitDisposable != null) {
            mTimeLimitDisposable.dispose();
        }
        mTimeLimitDisposable = Flowable.intervalRange(0, 3000, 0, 1, TimeUnit.MILLISECONDS)
                .onBackpressureDrop()
                .observeOn(mSchedulerProvider.ui())
                .subscribeOn(mSchedulerProvider.computation())
                .doOnError(throwable -> {

                })
                .doOnComplete(this::getUserDataDone)
                .subscribe();
    }

    private void getUserDataDone() {
        Log.e("aaa", "getUserDataDone");
        Log.e("aaa","当前线程ID:" +Thread.currentThread().getId());
        UserBean userBean = new UserBean("第一个名字", "第二个名字");
        users.postValue(userBean);
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        if (mTimeLimitDisposable != null) {
            mTimeLimitDisposable.dispose();
        }
    }
}

ViewModelFactory

public class ViewModelFactory extends ViewModelProvider.NewInstanceFactory {

    private static volatile ViewModelFactory INSTANCE;

    public static ViewModelFactory getInstance(){
        if(INSTANCE == null){
            synchronized (ViewModelFactory.class){
                if(INSTANCE == null){
                    INSTANCE = new ViewModelFactory();
                }
            }
        }
        return INSTANCE;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        return super.create(modelClass);
    }
}

ViewModelActivity

这里按钮点击实现了一个同步修改页面的操作,直接使用 setValue 。这边页面通过监听数据的变化来做修改

public class ViewModelActivity extends AppCompatActivity implements View.OnClickListener {
    private ActivityViewModelBinding mViewModelBinding;
    private UserViewModel mModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.
        mViewModelBinding = DataBindingUtil.setContentView(this, R.layout.activity_view_model);
        mViewModelBinding.setClick(this);
        Log.e("aaa","当前主线程111ID:" +Thread.currentThread().getId());
        mModel = new ViewModelProvider(this.getViewModelStore(),
                ViewModelFactory.getInstance()).get(UserViewModel.class);
        mModel.getUsers().observe(this, users -> {
            // update UI
            Log.e("aaa", "收到了");
            Log.e("aaa","当前主线程222ID:" +Thread.currentThread().getId());
            mViewModelBinding.tvAsyncName.setText(users.getFirstName());
        });
        mModel.getCurrentName().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String curName) {
                Log.e("aaa", "修改model的数据");
                mViewModelBinding.tvName.setText(curName);
            }
        });
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_click) {
            double index = (int) (Math.random() * 100);
            String anotherName = "随机展示:" + index;
            mModel.getCurrentName().setValue(anotherName);
        }
    }
}

以上就是用MVVM来完成整个业务的开发的具体代码。

利用ViewModel来实现两个子Fragment通信

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。想象一下主从 Fragment 的常见情况,假设您有一个 Fragment,在该 Fragment 中,用户从列表中选择一项,还有另一个 Fragment,用于显示选定项的内容。这种情况不太容易处理,因为这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。

如果使用ViewModel,将会有如下优势

  • Activity 不需要执行任何操作,也不需要对此通信有任何了解。

  • 除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。

  • 每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。

图片名称

MasterFragment 主fragment

public class MasterFragment extends Fragment {

    private SharedViewModel sharedViewModel;
    private Button mBtn;

   //....

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider 时,
        // 它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。
        sharedViewModel =  new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        mBtn.setOnClickListener(v -> {
            Log.e("aaa", "点击---->");
            double index = (int) (Math.random() * 100);
            String anotherName = "随机展示:" + index;
            sharedViewModel.select(anotherName);
        });

    }
}

DetailFragment 子fragment

public class DetailFragment extends Fragment {

    private TextView mTvText;

  //....

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
//        SharedViewModel model = new ViewModelProvider(this.getViewModelStore(),
//                ViewModelFactory.getInstance()).get(SharedViewModel.class);
        SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        model.getSelected().observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.e("aaa", "change----->" + s);
                mTvText.setText(s);
            }
        });
    }

}

SharedViewModel

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<String> selected = new MutableLiveData<String>();

    public void select(String item) {
        selected.setValue(item);
    }

    public LiveData<String> getSelected() {
        return selected;
    }
}

ViewModel

ViewModel 的生命周期

ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle。ViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失:对于 Activity,是在 Activity 完成时;而对于 Fragment,是在 Fragment 分离时。

在这里插入图片描述

示例代码链接

小结

通过上述例子,ViewModel的使用能帮我们提高我们的日常业务开发,在使用ViewPager中嵌套多个Fragment中,ViewModel能帮我们更好的解耦子fragment之间的通信,当我用了ViewModel之后,才发现使用此组件真香。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值