We’ve already implemented MVVM using Data Binding and covered LiveData and Data Binding in separate tutorials. Today, we’ll use LiveData with Data Binding in our MVVM Android Application. We’ll see how LiveData makes it easy to update the UI from the ViewModel.
Up until now, we’ve used Data Binding to update the View from the ViewModel. LiveData is a handy data holder that acts as a container over the data to be passed. The best thing about LiveData is that it is lifecycle aware. So if you are in the background, the UI won’t try to update. This saves us from a lot of crashes at runtime. We’ll use the MutableLiveData class since it provides public methods setValue()
and getValue()
. Let’s create a simple Login Application using the above concepts. We will first use LiveData as well as Two-way Data Binding and then refactor the Data Binding Observables to LiveData completely.
Add the following dependency in your app’s build.gradle:
android {
...
dataBinding {
enabled = true
}
...
}
dependencies {
...
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'com.android.support:design:28.0.0-beta01'
...
}
The LoginViewModelOld file would contain the old code and LoginViewModel file would contain the refactored code.
We’ve defined our Model in the User.java class:
package com.journaldev.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;
}
}
The code for the activity_main.xml is given below:
<?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.journaldev.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>
The ProgressBar would be displayed to simulate the login feature.
The code for the LoginViewModel.java is given below:
package com.journaldev.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);
}
}
An ObservableField is an object wrapper to make it observable. In the above code, we’ve encapsulated User inside a LiveData. Every time the user object is changed it will be observed in the MainActivity and the appropriate action would be taken. When the button is clicked, we set the ProgressBar to Visible. View.VISIBLE = 0
. View.GONE == 8
After a delay of 5 seconds, the email and password are validated and the TextInputLayout bindables are updated.
ObservableField isn’t lifecycle aware.
The MainActivity.java class is given below:
package com.journaldev.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.journaldev.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() {
@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();
}
});
}
}
In the above code, the observe method looks for changes in the User object that was contained in the MutableLiveData. It displays a toast with the username and password. Now, let’s replace the ObservableField with LiveData completely.
The code for the new LoginViewModel.java class is given below:
package com.journaldev.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);
}
}
The above class now extends ViewModel since we no longer need BaseObservable
. Now, we’ve changed the ObservableFields to MutableLiveData. Changes in the MutableLiveData would be automatically updated in the layout thanks to Data Binding. Our MainActivity.java class is now updated to:
package com.journaldev.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.journaldev.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() {
@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 can be used to create ViewModel instance too as done above. This method instantiates the ViewModel only once. Every subsequent call would reuse the instance. The LifecycleOwner is an interface that our Activity can bind to. The output of the above application in action is given below: As you can see, the Toast message isn’t shown when the user leaves the application. Since the LiveData is lifecycle aware. When you open the application again, the toast would be displayed. This brings an end to this tutorial. You can download the project from the link below:
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
Hello:) Thank you for your tutorials. I have a question to you: How can I add a spinner with mvvm ? I am looking up to internet but I can not understand. Can You help me?
- Esra Didem
Please make tutorial on mvvm rx java . Waiting for your tutorial
- Muhammad Zawawi Bin Manja
clear explanation…thank you…continue your good work…
- kajendhiran
Hi, the tutorial is fantastic and thanks a lot for this. However, I have a query though - In demonstrating the liveData example you had written this piece of code :- User user = new User(email.getValue(), password.getValue()); This code however violates the principle of Dependency Injection. What you be your recommendation to deal with this? Thanks in advance.
- Akash Biswas
why use mutablelivedata binding two way instead of livedata?
- Nam Anh
Found data binding errors. ****/ data binding error ****msg:Cannot find the getter for attribute ‘android:text’ with value type java.lang.String on com.google.android.material.textfield.TextInputLayout. file:D:\Android\MyBoond\app\src\main\res\layout\activity_registration.xml loc:71:12 - 95:67 ****\ data binding error **** I am getting this error. If anyone can help me. I am newbie in databinding.
- Faheem
> busy.setValue(8); //8 == View.GONE You can safely use View class as your ViewModel class is any accessing Android framework classes. JVM based testing will not be run here. > public boolean isEmailValid() { > return Patterns.EMAIL_ADDRESS.matcher(getEmail()).matches(); > } Model class should not access Pattern class as it is based on android framework. MVVM was originally created to help unit test pieces.
- Rohit Sharma
Will you have example MVVM databinding to display item in recycler from api and on item click display details of item
- sagar hudge
I read regarding MVVM integration with LiveData your article is very useful for me, I hope this article is useful every MVVM beginner who wants to understand very initial level… Thank you so much
- Ram Bhawan
I just wanted to say thank you for writing a really useful article.
- dlm