Tutorial

Android MVVM LiveData Data Binding

Published on August 3, 2022
author

Anupam Chugh

Android MVVM LiveData Data Binding

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.

MVVM LiveData Data Binding

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.

Getting Started

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'
    ...
}

Project Structure

android-mvvm-livedata-databinding-project The LoginViewModelOld file would contain the old code and LoginViewModel file would contain the refactored code.

Model

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;
    }

}


Layout

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.

ViewModel

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.

Refactoring ObservableField to LiveData

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: android mvvm live data data binding demo 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:

AndroidMVVMDataBindingLiveData

Github Project Link

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Anupam Chugh

author

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.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
JournalDev
DigitalOcean Employee
DigitalOcean Employee badge
March 26, 2021

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

    JournalDev
    DigitalOcean Employee
    DigitalOcean Employee badge
    December 14, 2019

    Please make tutorial on mvvm rx java . Waiting for your tutorial

    - Muhammad Zawawi Bin Manja

      JournalDev
      DigitalOcean Employee
      DigitalOcean Employee badge
      December 10, 2019

      clear explanation…thank you…continue your good work…

      - kajendhiran

        JournalDev
        DigitalOcean Employee
        DigitalOcean Employee badge
        October 31, 2019

        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

          JournalDev
          DigitalOcean Employee
          DigitalOcean Employee badge
          September 18, 2019

          why use mutablelivedata binding two way instead of livedata?

          - Nam Anh

            JournalDev
            DigitalOcean Employee
            DigitalOcean Employee badge
            September 11, 2019

            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

              JournalDev
              DigitalOcean Employee
              DigitalOcean Employee badge
              July 23, 2019

              > 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

                JournalDev
                DigitalOcean Employee
                DigitalOcean Employee badge
                July 11, 2019

                Will you have example MVVM databinding to display item in recycler from api and on item click display details of item

                - sagar hudge

                  JournalDev
                  DigitalOcean Employee
                  DigitalOcean Employee badge
                  April 25, 2019

                  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

                    JournalDev
                    DigitalOcean Employee
                    DigitalOcean Employee badge
                    January 3, 2019

                    I just wanted to say thank you for writing a really useful article.

                    - dlm

                      Try DigitalOcean for free

                      Click below to sign up and get $200 of credit to try our products over 60 days!

                      Sign up

                      Join the Tech Talk
                      Success! Thank you! Please check your email for further details.

                      Please complete your information!

                      Featured on Community

                      Get our biweekly newsletter

                      Sign up for Infrastructure as a Newsletter.

                      Hollie's Hub for Good

                      Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

                      Become a contributor

                      Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

                      Welcome to the developer cloud

                      DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

                      Learn more