MVVM项目实战之路-搭建一个登录界面


最近有一个新任务,给公司内部开发一个App,需求初始很简单,刚好之前一直在看MVVM这种开发模式,所以决定用Data Binding Library以及ViewModel等实现这种模式,因为代码和之前写法确实有很大不同,所以决定将这个app实现过程中这种新的模式的一些具体业务场景的代码写法用博客记录下来,希望对其他人有用。

本文基于Android Studio3.0

一.MVVM

首先,当然要先简单了解下什么是MVVM开发模式,来看一张图:

mvvm.png

可以看出,在MVVM中,我们的代码结构分为三层:
  • View 这里主要进行视图控件的一些初始设置,不应该有任何的数据逻辑操作
  • Model 在这层中,会定义我们的实体类,以及所有的业务逻辑操作,比如通过数据库或者网络来操作数据等都应该在这里进行
  • ViewModel 就像上图一样,ViewModel是连接View与Model的中间桥梁,ViewModel与Model直接交互,处理完业务逻辑后,通过DataBinding将数据变化反应到用户界面上。

MVVM的具体意义以及与MVC,MVP等的对比这里不再赘述。

二.实现一个登录界面

这里,我们先用这种模式实现一个登录界面,界面简单如下:

2.png

需求很简单:用户输入用户名,密码,点击登录按钮后调用接口进行检查,成功则跳转到下一个界面,失败则提示错误信息。

废话不多说,It`s time to show code!

  1. 启用DataBinding 首先,要保证你的Gradle插件版本要大于 1.5.0-alpha1及以上(现在基本都比这个版本高了吧),然后在app下的build.gradle文件添加以下代码:
dataBinding {
    enabled = true
}

2.添加ViewModel and LiveData

implementation "android.arch.lifecycle:extensions:1.0.0"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0"

3.登录页面activity_login.xml

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="viewmodel"
            type="com.example.zq.mvvmdemo.user.LoginViewModel" />
    </data>

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


        <EditText
            android:layout_width="240dp"
            android:layout_height="40dp"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="100dp"
            android:hint="@string/login_name"
            android:text="@={viewmodel.loginName}" />

        <EditText
            android:layout_width="240dp"
            android:layout_height="40dp"
            android:layout_below="@+id/name"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="30dp"
            android:hint="@string/login_password"
            android:text="@={viewmodel.loginPass}" />

        <Button
            android:id="@+id/submit"
            android:layout_width="240dp"
            android:layout_height="40dp"
            android:layout_below="@+id/password"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="40dp"
            android:text="@string/login"
            android:textSize="16dp" />
    </LinearLayout>

</layout>

从这里开始,就会发现和我们之前的写法有很大的区别了,之前的xml文件根节点是LinearLayout或者RelativeLayout等布局,但是在使用DataBinding后,我们的xml文件可以概括成这样:

<?xml version="1.0" encoding="utf-8"?>
<layout >
    <data>
    .......
    </data>
    <LinearLayout>
        ........
        自己的布局
        ........
    </LinearLayout>
</layout>

最外层以layout标签包裹,里边用data标签表示我们要绑定的数据的名字以及类型,然后就是我们自己的布局。

之前我们已经知道,ViewModel是View与Model层交互的桥梁,所以具体用到的业务数据,比如这里用户名,密码等我这里全部放到了ViewModel中,然后将ViewModel与View进行绑定:

<data>
        <variable
            name="viewmodel"
            type="com.example.zq.mvvmdemo.user.LoginViewModel" />
 </data>

4.LoginViewModel文件

public class LoginViewModel extends AndroidViewModel {
    private static final String TAG = "LoginViewModel";

    private final SingleLiveEvent<String> mOpenUserList = new SingleLiveEvent<>();
    private final SnackbarMessage mSnackbarText = new SnackbarMessage();
    private final Context mContext; // To avoid leaks, this must be an Application Context.

    public final ObservableField<String> loginName = new ObservableField<>();
    public final ObservableField<String> loginPass = new ObservableField<>();

    public LoginViewModel(Application mContext) {
        super(mContext);
        this.mContext = mContext.getApplicationContext();
    }

    public SingleLiveEvent<String> getmOpenUserList() {
        return mOpenUserList;
    }

    SnackbarMessage getSnackbarMessage() {
        return mSnackbarText;
    }

    private void login(String loginName, String loginPass) {

        if (TextUtils.isEmpty(loginName)) {
            mSnackbarText.setValue(mContext.getString(R.string.login_name_not_input));
            return;
        }
        if (TextUtils.isEmpty(loginPass)) {
            mSnackbarText.setValue(mContext.getString(R.string.login_pass_not_input));
            return;
        }


        mOpenUserList.setValue("123456");

    }


    public void login() {
        login(loginName.get(), loginPass.get());
    }
}

首先我们来看loginName和loginPass这两个变量,ObservableField为DataBinding中提供的一个类,它使我们的对象变得可观测,即修改界面上的值,对应的loginName和loginPass的值就会改变,反之亦然。

再看mOpenUserList与mSnackbarText,它们是LiveData类型的,LiveData是一个数据持有类,并且在给定的生命周期中其变化是可观测的,这里用来处理ViewModels与 UI views (activities and fragments)的一些交互。

login()方法由点击登录按钮后触发,这里注意,因为loginName和loginPass已经与我们的视图文件绑定在一起了,所以就不用在调用的时候从EditText获取文本内容再传进来了。

getmOpenUserList()与getSnackbarMessage()将mOpenUserList与mSnackbarText公布给 UI views (activities and fragments),来处理一些交互,在这个例子里,主要是弹出提示以及跳转页面。

5.LoginActivity文件

public class LoginActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ViewModelFactory factory = ViewModelFactory.getInstance(getApplication());
        final LoginViewModel loginViewModel = ViewModelProviders.of(this, factory).get(LoginViewModel.class);

        final ActivityLoginBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_login);
        binding.setViewmodel(loginViewModel);

        loginViewModel.getmOpenUserList().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                Intent intent = new Intent(LoginActivity.this, UserListActivity.class);
                intent.putExtra("token", s);
                startActivity(intent);
                finish();
            }
        });

        loginViewModel.getSnackbarMessage().observe(this, new SnackbarMessage.SnackbarObserver() {
            @Override
            public void onNewMessage(String message) {
                SnackbarUtils.showSnackbar(binding.getRoot(), message);
            }
        });

        findViewById(R.id.submit).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                loginViewModel.login();
            }
        });
    }

}

这里就相当于View层,按照前面说的,这里应该只有一些界面的设置,不应该有任何的逻辑处理。ActivityLoginBinding是自动生成的,注意,在写完xml文件后要Build-Make Project一下,才会生成这个文件。

梳理一下逻辑:

点击登录按钮后,会调用LoginViewModel中的login()方法,进行参数的检查,如果参数不合法,为mSnackbarText设置对应的文案提示,因为我们在Activity已经监测了mSnackbarText的 变化,当它的值发生变化后,会通过回调通知回来,我们可以进行提示:

SnackbarUtils.showSnackbar(binding.getRoot(), message);

当参数全部合法后,改变mOpenUserList,同样会触发回调,进而跳转到下一个界面:

Intent intent = new Intent(LoginActivity.this, UserListActivity.class);
                intent.putExtra("token", s);
                startActivity(intent);
                finish();

以上就是用这种新的开发模式来完成这个登录需求的一个记录,在检查参数这里写的比较简单,其实应该用接口来检验,这又牵扯到一层封装,这里暂时先不写。可以看出,用这种模式写法与之前确实有很大不同,其中的一些问题可能描述的也不是很清楚,欢迎大佬给出建议和指正错误,会慢慢改正。

demo代码地址:https://github.com/SolveBugs/MVVMDemo

优质内容筛选与推荐>>
1、计算几何算法
2、devstack中screen的使用
3、UML需求分析步骤实例解析
4、PHP Cookbook读书笔记 – 第10章数据库访问
5、ListView


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号