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 条评论
登录 后参与评论

相关文章

来自专栏C#

DotNet加密方式解析--非对称加密

    新年新气象,也希望新年可以挣大钱。不管今年年底会不会跟去年一样,满怀抱负却又壮志未酬。(不过没事,我已为各位卜上一卦,卦象显示各位都能挣钱...)...

4828
来自专栏张善友的专栏

LINQ via C# 系列文章

LINQ via C# Recently I am giving a series of talk on LINQ. the name “LINQ via C...

2625
来自专栏张善友的专栏

Silverlight + Model-View-ViewModel (MVVM)

     早在2005年,John Gossman写了一篇关于Model-View-ViewModel模式的博文,这种模式被他所在的微软的项目组用来创建Expr...

2938
来自专栏陈仁松博客

ASP.NET Core 'Microsoft.Win32.Registry' 错误修复

今天在发布Asp.net Core应用到Azure的时候出现错误InvalidOperationException: Cannot find compilati...

4818
来自专栏一个会写诗的程序员的博客

Spring Reactor 项目核心库Reactor Core

Non-Blocking Reactive Streams Foundation for the JVM both implementing a Reactiv...

2132
来自专栏转载gongluck的CSDN博客

cocos2dx 打灰机

#include "GamePlane.h" #include "PlaneSprite.h" #include "BulletNode.h" #include...

5346
来自专栏我和未来有约会

Kit 3D 更新

Kit3D is a 3D graphics engine written for Microsoft Silverlight. Kit3D was inita...

2506
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3095
来自专栏闻道于事

js登录滑动验证,不滑动无法登陆

js的判断这里是根据滑块的位置进行判断,应该是用一个flag判断 <%@ page language="java" contentType="text/html...

6708
来自专栏张善友的专栏

Mix 10 上的asp.net mvc 2的相关Session

Beyond File | New Company: From Cheesy Sample to Social Platform Scott Hansel...

2537

扫码关注云+社区