Android MVVM LiveData数据绑定
我们已经使用数据绑定实现了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是生命周期感知的。