LoginSignup
28
21

More than 5 years have passed since last update.

Live Data + DataBindを使った簡単なアプリ

Posted at

検証環境

この記事の内容は、以下の環境で検証しました。

  • Java:open jdk 1.8.0_152
  • Kotlin 1.2.10
  • Android Studio 3.0.2

ゴール

これまで作成したきたアプリは、ユーザからのアクションがありきになっていました。
例えば、ボタンを押したときのイベントで画面遷移や何かしらの処理を行っていました。

今回は、EditTextで入力するとリアルタイムで処理をするイベントを使わないアプリを作っていきます。
01_最終ゴール.png

実現方法の全体像

入力された値や答えを保持するModel(POJO)をどうやってリアルタイムで処理したものか・・・。と考えていきます。

02_全体像.png

まずは、Modelを常に監視して、計算するクラスが必要になります。監視する人をObserverと呼びます。ObserverはLiveData関連のクラス群の1つです。

03_オブザーバー.png

もちろんModelをObserverに対応する形にする必要あります。

04_POJOの修正.png

更に、EditTextに入力された値を、Modelに代入する人も必要になってきます。画面とModelを同期させる人をデータバインドと呼びます。データバインドはDataBind関連のクラス群の1つです。

05_データバインド.png

結果的に下記のような構成になります。

05_最終全体像.png

各機能を実現するクラスは下記の通りです。
今後の実装の説明では、図の番号の順番で説明します。

06_クラス名で表記.png

実装

各機能の実装を見ていきます。

事前準備

Gradle

Live DataやDataBindを利用するために、設定を行っていきます。

Live Dataの設定

dependenciesに下記を追記します。

dependencies {
    // ViewModel and LiveData
    implementation "android.arch.lifecycle:extensions:1.1.0"
}

DataBindの設定

dependenciesに下記を追記します。

android {
    dataBinding{
        enabled = true
    }
}
dependencies {
    kapt "com.android.databinding:compiler:3.0.1"
}
kapt {
    generateStubs = true
}

Model

コードの全体像

先に説明したように、Observerを設定する必要があるクラスは下記のように記述します。

CalcModel
class CalcModel : ViewModel() {
    var first = MutableLiveData<String>()

    var second = MutableLiveData<String>()

    var result = "0"

    init {
        first.value = "0"
        second.value = "0"
    }
}

コードの詳細

ViewModelクラスを継承します。

class CalcModel : ViewModel()

Observerを設定するフィールドはMutableLiveDataクラスにする必要があります。

var first = MutableLiveData<String>()
var second = MutableLiveData<String>()

Observer

コードの全体像

MainActivity
package jp.co.casareal.mvvmsample

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.databinding.DataBindingUtil
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import jp.co.casareal.mvvmsample.databinding.ActivityMainBinding
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    lateinit var calcModel: CalcModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val activityMainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        calcModel = ViewModelProviders.of(this).get(CalcModel::class.java)
        activityMainBinding.model = calcModel
    }

    override fun onResume() {
        super.onResume()

        val observer = Observer<String>() {
            if (firstEdit.text.isNotBlank() && secondEdit.text.isNotBlank())
                resultText.text = (calcModel.first.value!!.toInt() + calcModel.second.value!!.toInt()).toString()
        }

        calcModel.first.observe(this, observer)
        calcModel.second.observe(this, observer)

    }
}

コードの詳細

ViewModelを継承したクラスのオブジェクトを取得します。(本サンプルではonCreateメソッド内に実装しています。)
オブジェクト取得にはViewModelProvidersクラスのof関数でFragmentActivityを継承したクラスのオブジェクトを渡します。戻り値がViewModelProvidersクラスのオブジェクトです。
get関数を呼び出してViewModelクラスのオブジェクトを取得します。

 calcModel = ViewModelProviders.of(this).get(CalcModel::class.java)

Observerインターフェイスの実装オブジェクトを生成します。
本サンプルでは、左辺と右辺を整数に変換して、加算した結果を答えのTextViewに設定しています。

val observer = Observer<String>() {
    if (firstEdit.text.isNotBlank() && secondEdit.text.isNotBlank())
        resultText.text = (calcModel.first.value!!.toInt() + calcModel.second.value!!.toInt()).toString()
}

ViewModelで定義したObserverを設定できるフィールドにObserverを設定します。

calcModel.first.observe(this, observer)
calcModel.second.observe(this, observer)

データバインド

コードの全体像

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

    <data>
        <variable
            name="model"
            type="jp.co.casareal.mvvmsample.CalcModel"></variable>
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="jp.co.casareal.mvvmsample.MainActivity">

        <TextView
            android:id="@+id/resultText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="16dp"
            android:text="@{model.result}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginStart="32dp"
            android:layout_marginTop="8dp"
            android:text="+"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toEndOf="@+id/firstEdit"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/firstEdit"
            android:layout_width="71dp"
            android:layout_height="54dp"
            android:layout_marginBottom="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:ems="10"
            android:text="@={model.first.value}"
            android:inputType="number"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/secondEdit"
            android:layout_width="74dp"
            android:layout_height="47dp"
            android:layout_marginBottom="8dp"
            android:layout_marginStart="36dp"
            android:layout_marginTop="8dp"
            android:ems="10"
            android:text="@={model.second.value}"
            android:inputType="number"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toEndOf="@+id/textView"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/equalText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:text="="
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/resultText"
            app:layout_constraintHorizontal_bias="0.436"
            app:layout_constraintStart_toEndOf="@+id/secondEdit"
            app:layout_constraintTop_toTopOf="parent" />

    </android.support.constraint.ConstraintLayout>
</layout>
MainActivity.kt
package jp.co.casareal.mvvmsample

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.databinding.DataBindingUtil
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import jp.co.casareal.mvvmsample.databinding.ActivityMainBinding
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    lateinit var calcModel: CalcModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val activityMainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        calcModel = ViewModelProviders.of(this).get(CalcModel::class.java)
        activityMainBinding.model = calcModel
    }

    override fun onResume() {
        super.onResume()

        val observer = Observer<String>() {
            if (firstEdit.text.isNotBlank() && secondEdit.text.isNotBlank())
                resultText.text = (calcModel.first.value!!.toInt() + calcModel.second.value!!.toInt()).toString()
        }

        calcModel.first.observe(this, observer)
        calcModel.second.observe(this, observer)

    }
}

コードの詳細

レイアウトリソースファイルの全体構成は下記のようになっています。

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="xmlないでの変数名みたいなもの"
            type="バインディングするクラスの完全クラス名"></variable>
    </data>
    <ビューグループ(LinearLayoutとかConstraintLayoutなど>
    〜〜EditTextとかTextViewとかとか〜〜
    </ビューグループ>
</layout>

バインドする際の変数名とその完全クラス名をdataタグないで定義します。

<data>
    <variable
        name="model"
        type="jp.co.casareal.mvvmsample.CalcModel"></variable>
</data>

バインディングするデータを設定するには、下記のように記述します。

<TextView
    android:id="@+id/resultText"
    android:text="@{model.result}"
/>
<EditText
    android:id="@+id/firstEdit"
    android:text="@={model.first.value}"
/>

バインドの記述方法は2種類存在します。

@{dataのname属性.フィールド}

は単一方向のバインディングです。「ViewModel⇛画面」

@={dataのname属性.フィールド}

は双方向のバインディングです。「ViewModel⇔画面」

ViewModelでフィールドがMutableLiveDataクラスの場合は、下記のように記述し、値を取得します。

@={dataのname属性.フィールド.value}

ViewModelとバインドするには、Kotlin側で設定が必要です。バインドするために、「ActivityMainBinding」を取得しています。「ActivityMainBinding」は自動生成されるクラスです。
クラスの命名は、「レイアウトリソースファイルをキャメルケース+Binding」です。
通常であれば存在するsetContentViewメソッドは削除します。その代わり、DataBindingUtilクラスのsetContentViewを呼び出します。
ジェネリクスでは、Bindingクラス名を指定し、第1引数にはActivity、第2引数にはレイアウトリソースファイルを指定します。

val activityMainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

ActivityMainBindingのオブジェクトが取得できたら、レイアウトリソースファイルのdataタグのname属性で指定した名前のフィールドにViewModelクラスのオブジェクトを設定します。

activityMainBinding.model = calcModel

まとめ

バインディングの方法はLive Data+DataBind以外にも存在しますが、Live Dataを利用することによりより簡潔に記述できるようになっているそうです。
いつか、比較できるような記事を書けたらと思います。

28
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
21