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

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

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

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

今回は、EditTextで入力するとリアルタイムで処理をするイベントを使わないアプリを作っていきます。
![01_最終ゴール.png](https://qiita-image-store.s3.amazonaws.com/0/99960/b31cff0a-9615-9d55-602b-cbe70fa88a12.png)


# 実現方法の全体像

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

![02_全体像.png](https://qiita-image-store.s3.amazonaws.com/0/99960/d5d575c7-67e0-3201-2430-fff0e4842564.png)


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

![03_オブザーバー.png](https://qiita-image-store.s3.amazonaws.com/0/99960/30af0076-08ce-25d6-5cd6-fd5c39585d36.png)

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

![04_POJOの修正.png](https://qiita-image-store.s3.amazonaws.com/0/99960/3e2620eb-bc20-da9d-f3cd-4881d79d687b.png)


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

![05_データバインド.png](https://qiita-image-store.s3.amazonaws.com/0/99960/6129e401-a45d-d084-ee53-29ee21266b25.png)

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

![05_最終全体像.png](https://qiita-image-store.s3.amazonaws.com/0/99960/e512a583-4604-7a1f-20e5-3066a19676f0.png)


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

![06_クラス名で表記.png](https://qiita-image-store.s3.amazonaws.com/0/99960/440fa27a-9504-16f8-c6c4-06aab68fb750.png)

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

## 事前準備

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

#### Live Dataの設定
dependenciesに下記を追記します。

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

#### DataBindの設定
dependenciesに下記を追記します。

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

### Model
#### コードの全体像
先に説明したように、Observerを設定する必要があるクラスは下記のように記述します。

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

    var second = MutableLiveData<String>()

    var result = "0"

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

#### コードの詳細
ViewModelクラスを継承します。

```kotlin
class CalcModel : ViewModel()
```
Observerを設定するフィールドはMutableLiveDataクラスにする必要があります。

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

### Observer
#### コードの全体像

```kotlin: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クラスのオブジェクトを取得します。

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

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

```kotlin
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を設定します。

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

### データバインド
#### コードの全体像

```xml: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>
```

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

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

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

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

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

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

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

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

```text
@={dataのname属性.フィールド}
```
は双方向のバインディングです。「ViewModel⇔画面」

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

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

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

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

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

```kotlin
activityMainBinding.model = calcModel
```

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