アンドロイドのMVVM LiveDataデータバインディング

MVVMを使用したデータバインディングの実装と別々のチュートリアルでLiveDataとData Bindingをカバーしました。今日は、MVVMを使用したAndroidアプリケーションにおいてLiveDataとData Bindingを組み合わせます。LiveDataがViewModelからUIの更新を簡単にする方法を見ていきましょう。

MVVMアーキテクチャ、LiveData、Data Binding

これまで、私たちはViewModelからViewを更新するためにData Bindingを使用してきました。LiveDataは、渡されるデータのコンテナとして機能する便利なデータホルダーです。LiveDataの最大の利点は、ライフサイクルを認識していることです。つまり、バックグラウンドにいる場合、UIは更新しようとしません。これにより、ランタイムでのクラッシュが大幅に減ります。public methods setValue()とgetValue()を提供するため、MutableLiveDataクラスを使用します。上記の概念を使用して、シンプルなログインアプリケーションを作成しましょう。最初はLiveDataとTwo-way Data Bindingを使用し、その後、Data Binding Observablesを完全に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.scdev.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.scdev.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>

ログイン機能をシミュレーションするために、プログレスバーが表示されます。

ビューモデル

以下にLoginViewModel.javaのコードが記載されています。

package com.scdev.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.scdev.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.scdev.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();
            }
        });

    }
}

上記のコードでは、observeメソッドはMutableLiveDataに含まれていたUserオブジェクトの変更を監視します。ユーザー名とパスワードを含むトーストを表示します。それでは、ObservableFieldを完全にLiveDataで置き換えましょう。

ObservableFieldをLiveDataにリファクタリングする。

新しいLoginViewModel.javaクラスのコードは以下の通りです:

package com.scdev.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);
    }
}

上記のクラスは、BaseObservableはもはや必要ないため、現在ViewModelを継承しています。ObservableFieldsはMutableLiveDataに変更されました。MutableLiveDataの変更は、Data Bindingのおかげで自動的にレイアウトに反映されます。MainActivity.javaクラスは以下のように更新されました。

package com.scdev.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.scdev.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を使用してViewModelインスタンスを作成することもできます。このメソッドはViewModelを一度だけインスタンス化します。以降のすべての呼び出しでは、同じインスタンスが再利用されます。LifecycleOwnerは、私たちのActivityがバインドできるインターフェースです。上記のアプリケーションの出力は以下の通りです:表示されるように、ユーザーがアプリケーションを離れると、Toastメッセージは表示されません。LiveDataはライフサイクルに対応しているため、アプリケーションを再度開くと、トーストが表示されます。これでチュートリアルは終了です。プロジェクトは以下のリンクからダウンロードできます。

AndroidMVVMDataBindingLiveDataを日本語で表現すると、次のようになります:

アンドロイドのMVVMデータバインディングライブデータ

GitHubのプロジェクトのリンク

コメントを残す 0

Your email address will not be published. Required fields are marked *