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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏jianhuicode

如何使用融云地图,文件等插件--融云 Android SDK 2.8.0+ Extension 开发文档

转载自融云 Android SDK 2.8.0+ Extension 开发文档 融云 SDK 2.8.0 后对 会话界面输入区域、+号扩展区域、语音消息、Emo...

24710
来自专栏青玉伏案

Android开发之Activity的生命周期以及加载模式

本篇博客就来好好的搞一下Activity的生命周期,如果搞过iOS的小伙伴的话,Activity的生命周期和iOS中ViewController的生命周期非常类...

1927
来自专栏Ryan Miao

Javascript跨域后台设置拦截

子域名之间互相访问需要跨域 结论放在开头: 服务端必须设置允许跨域 客户端带cookie需要设置withCredentials 无论服务端是否允许跨域,该req...

2648
来自专栏Android中高级开发

首个hybird商业项目踩坑总结

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索...

731
来自专栏玩转全栈

flutter接入现有的app详细介绍

接入的方式,我是参考的官方的介绍文档,我这里尝试的是android的接入方式,还算比较顺利。

2893
来自专栏移动端开发

Android学习--ListView

这篇文章用于总结自己这两天学到的安卓的ListView和RecyclerView 的笔记,以及从我这个iOS开发者的角度去理解和学习这两个控件,会比较一下他们个...

1979
来自专栏Android先生

Android App优化之提升你的App启动速度之实例挑战

以之前写的Github App:https://github.com/mingjunli/GithubApp为例.

903
来自专栏向治洪

高仿今日头条

高仿今日头条 --- 第一篇:(android高仿系列)今日头条 --新闻阅读器 (一) 上次,已经完成了头部新闻分类栏目的拖动效果。 这篇文章是继续去完善...

2029
来自专栏Android机动车

混合开发之WebView秘笈

注意: 每个 Application 只调用一次 WebSettings.setAppCachePath(),WebSettings.setAppCacheMa...

1063
来自专栏Android开发指南

14.极光推送

35611

扫码关注云+社区