Android MVVM LiveData数据绑定

时间:2020-02-23 14:29:05  来源:igfitidea点击:

我们已经使用数据绑定实现了MVVM,并在单独的教程中介绍了LiveData和Data Binding。
今天,我们将在MVVM Android应用程序中使用带数据绑定的LiveData。
我们将看到LiveData如何使从ViewModel更新UI变得容易。

MVVM LiveData数据绑定

到目前为止,我们已经使用数据绑定从ViewModel更新视图。
LiveData是一个方便的数据持有者,充当要传递的数据的容器。
LiveData最好的事情是它具有生命周期意识。
因此,如果您在后台,则UI不会尝试更新。

这使我们免于在运行时发生大量崩溃。

我们将使用MutableLiveData类,因为它提供了公共方法setValue()getValue()

让我们使用以上概念创建一个简单的登录应用程序。
我们将首先使用LiveData以及双向数据绑定,然后将数据绑定可观察对象完全重构为LiveData。

入门

在应用的build.gradle中添加以下依赖项:

android {
  ...

  dataBinding {
      enabled = true
  }
  ...
}

dependencies {
  ...
  implementation 'android.arch.lifecycle:extensions:1.1.1'
  implementation 'com.android.support:design:28.0.0-beta01'
  ...
}

项目结构

LoginViewModelOld文件将包含旧代码,而LoginViewModel文件将包含重构代码。

模型

我们已经在User.java类中定义了模型:

package com.theitroad.androidmvvmdatabindinglivedata;

import android.util.Patterns;

public class User {

  private String mEmail;
  private String mPassword;

  public User(String email, String password) {
      mEmail = email;
      mPassword = password;
  }

  public String getEmail() {
      if (mEmail == null) {
          return "";
      }
      return mEmail;
  }

  public String getPassword() {

      if (mPassword == null) {
          return "";
      }
      return mPassword;
  }

  public boolean isEmailValid() {
      return Patterns.EMAIL_ADDRESS.matcher(getEmail()).matches();
  }

  public boolean isPasswordLengthGreaterThan5() {
      return getPassword().length() > 5;
  }

}

布局

下面给出了activity_main.xml的代码:

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

  <data>

      <variable
          name="loginViewModel"
          type="com.theitroad.androidmvvmdatabindinglivedata.LoginViewModel" 
  </data>

  <ScrollView
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <LinearLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_gravity="center"
          android:layout_margin="8dp"
          android:orientation="vertical">

          <android.support.design.widget.TextInputLayout
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              app:error="@{loginViewModel.errorEmail}"
              app:errorEnabled="true">

              <EditText
                  android:id="@+id/inEmail"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:hint="Email"
                  android:inputType="textEmailAddress"
                  android:padding="8dp"
                  android:text="@={loginViewModel.email}" 

          </android.support.design.widget.TextInputLayout>

          <android.support.design.widget.TextInputLayout
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              app:error="@{loginViewModel.errorPassword}"
              app:errorEnabled="true">

              <EditText
                  android:id="@+id/inPassword"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:hint="Password"
                  android:inputType="textPassword"
                  android:padding="8dp"
                  android:text="@={loginViewModel.password}" 

          </android.support.design.widget.TextInputLayout>

          <Button
              android:id="@+id/button"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginTop="8dp"
              android:onClick="@{()-> loginViewModel.onLoginClicked()}"
              android:text="LOGIN" 

          <ProgressBar
              android:id="@+id/progressBar"
              style="?android:attr/progressBarStyleLarge"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_gravity="center_horizontal"
              android:layout_marginTop="8dp"
              android:visibility="@{loginViewModel.busy}" 

      </LinearLayout>

  </ScrollView>

</layout>

将显示ProgressBar以模拟登录功能。

视图模型

下面给出了LoginViewModel.java的代码:

package com.theitroad.androidmvvmdatabindinglivedata;

import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.databinding.ObservableField;
import android.os.Handler;
import android.support.annotation.NonNull;

public class LoginViewModel extends BaseObservable {
  private String email;
  private String password;
  private int busy = 8;
  public final ObservableField<String> errorPassword = new ObservableField<>();
  public final ObservableField<String> errorEmail = new ObservableField<>();

  public LoginViewModel() {
  }

  private MutableLiveData<User> userMutableLiveData;

  LiveData<User> getUser() {
      if (userMutableLiveData == null) {
          userMutableLiveData = new MutableLiveData<>();
      }

      return userMutableLiveData;
  }

  @Bindable
  @NonNull
  public String getEmail() {
      return this.email;
  }

  public void setEmail(@NonNull String email) {
      this.email = email;
      notifyPropertyChanged(BR.email);
  }

  @Bindable
  @NonNull
  public String getPassword() {
      return this.password;
  }

  public void setPassword(@NonNull String password) {
      this.password = password;
      notifyPropertyChanged(BR.password);
  }

  @Bindable
  public int getBusy() {
      return this.busy;
  }

  public void setBusy(int busy) {
      this.busy = busy;
      notifyPropertyChanged(BR.busy);
  }

  public void onLoginClicked() {

      setBusy(0); //View.VISIBLE
      new Handler().postDelayed(new Runnable() {
          @Override
          public void run() {

              User user = new User(getEmail(), getPassword());

              if (!user.isEmailValid()) {
                  errorEmail.set("Enter a valid email address");
              } else {
                  errorEmail.set(null);
              }

              if (!user.isPasswordLengthGreaterThan5())
                  errorPassword.set("Password Length should be greater than 5");
              else {
                  errorPassword.set(null);
              }

              userMutableLiveData.setValue(user);
              setBusy(8); //8 == View.GONE

          }
      }, 5000);
  }
}

ObservableField是使它可观察的对象包装。
在上面的代码中,我们将User封装在LiveData中。
每次更改用户对象时,都会在MainActivity中对其进行观察,并会采取适当的措施。

单击按钮后,我们将ProgressBar设置为Visible。
View.VISIBLE = 0。
View.GONE == 8

延迟5秒后,将验证电子邮件和密码,并更新TextInputLayout可绑定对象。

ObservableField不了解生命周期。

MainActivity.java类如下所示:

package com.theitroad.androidmvvmdatabindinglivedata;

import android.arch.lifecycle.Observer;
import android.databinding.DataBindingUtil;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

import com.theitroad.androidmvvmdatabindinglivedata.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

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

      ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
      LoginViewModel loginViewModel = new LoginViewModel();
      binding.setLoginViewModel(loginViewModel);

      loginViewModel.getUser().observe(this, new Observer<User>() {
          @Override
          public void onChanged(@Nullable User user) {
              if (user.getEmail().length() > 0 || user.getPassword().length() > 0)
                  Toast.makeText(getApplicationContext(), "email : " + user.getEmail() + " password " + user.getPassword(), Toast.LENGTH_SHORT).show();
          }
      });

  }
}

在上面的代码中,observe方法在MutableLiveData中包含的User对象中查找更改。
它显示带有用户名和密码的吐司。

现在,让我们用LiveData完全替换ObservableField。

将ObservableField重构为LiveData

下面给出了新的LoginViewModel.java类的代码:

package com.theitroad.androidmvvmdatabindinglivedata;

import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import android.os.Handler;

public class LoginViewModel extends ViewModel {

  public MutableLiveData<String> errorPassword = new MutableLiveData<>();
  public MutableLiveData<String> errorEmail = new MutableLiveData<>();

  public MutableLiveData<String> email = new MutableLiveData<>();
  public MutableLiveData<String> password = new MutableLiveData<>();
  public MutableLiveData<Integer> busy;

  public MutableLiveData<Integer> getBusy() {

      if (busy == null) {
          busy = new MutableLiveData<>();
          busy.setValue(8);
      }

      return busy;
  }

  public LoginViewModel() {

  }

  private MutableLiveData<User> userMutableLiveData;

  LiveData<User> getUser() {
      if (userMutableLiveData == null) {
          userMutableLiveData = new MutableLiveData<>();
      }

      return userMutableLiveData;
  }

  public void onLoginClicked() {

      getBusy().setValue(0); //View.VISIBLE
      new Handler().postDelayed(new Runnable() {
          @Override
          public void run() {

              User user = new User(email.getValue(), password.getValue());

              if (!user.isEmailValid()) {
                  errorEmail.setValue("Enter a valid email address");
              } else {
                  errorEmail.setValue(null);
              }

              if (!user.isPasswordLengthGreaterThan5())
                  errorPassword.setValue("Password Length should be greater than 5");
              else {
                  errorPassword.setValue(null);
              }

              userMutableLiveData.setValue(user);
              busy.setValue(8); //8 == View.GONE

          }
      }, 3000);
  }
}

上面的类现在扩展了ViewModel,因为我们不再需要BaseObservable

现在,我们将ObservableFields更改为MutableLiveData。
由于数据绑定,MutableLiveData中的更改将在布局中自动更新。

我们的MainActivity.java类现在更新为:

package com.theitroad.androidmvvmdatabindinglivedata;

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.databinding.DataBindingUtil;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

import com.theitroad.androidmvvmdatabindinglivedata.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

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

      ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
      LoginViewModel loginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
      binding.setLoginViewModel(loginViewModel);
      binding.setLifecycleOwner(this);
      

      loginViewModel.getUser().observe(this, new Observer<User>() {
          @Override
          public void onChanged(@Nullable User user) {
              if (user.getEmail().length() > 0 || user.getPassword().length() > 0)
                  Toast.makeText(getApplicationContext(), "email : " + user.getEmail() + " password " + user.getPassword(), Toast.LENGTH_SHORT).show();
          }
      });

  }
}

如上所述,ViewModelProviders.of也可用于创建ViewModel实例。
此方法仅实例化ViewModel一次。
每个后续调用将重用实例。

LifecycleOwner是我们的Activity可以绑定的接口。

上面应用程序的输出如下:

如您所见,当用户离开应用程序时,不会显示Toast消息。
由于LiveData是生命周期感知的。