DataBindingとViewBindingの使い方と使い分け

# はじめに
既存のプロジェクトにDataBindig/ViewBindingのどちらを適用するか悩んでいましたが、画面によってロジックの複雑さが異なっていたため、使い分けて適用するのがベストかなと思い、使い分けたポイントと使い方について書いていきたいと思います。
そもそも、DataBindingとViewBindingについては簡単に説明すると以下となります。
- DataBinding
2015年に登場し、XMLで定義したレイアウトと、そのレイアウトで使用されるデータモデルの連携をし易くするのを目的として提供されました。双方向バインティングが出来るのが特徴です。
- ViewBinding
2019年に登場し、DataBindingに比べてシンプルかつ軽量な代替手段として提供されました。特に、シンプルなUIとロジックで複雑な双方向バインディングを必要としないケースでのViewの参照方法を簡単にするために設計されています。

これから紹介するコードは、ベースとなるプロジェクトが以下となりますので、参考にして頂ければと思います。

https://github.com/highcom/PasswordMemoPublic

# DataBindingとViewBindingの有効化
`app/build.gradle`に以下を設定
```gradle
android {
    buildFeatures {
        viewBinding true
        dataBinding true
    }
}
```
上記を設定するだけで基本的には使えるようになります。ただし、使っているkotlinやライブラリのバージョンが低い場合はある程度上げないといけないので注意です。私の場合は
- Kotlin:1.6.21→1.8.22
- Room:2.4.2→2.5.2

にバージョンを上げました。

# DataBindingの使い方と使い所
### 使い所について
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545703/621df043-51d4-5486-8b5d-67503ee3de8e.png">

上記のログイン画面にて、
- EditTextレイアウトに入力された文字列を照合するためにViewModelで参照
- ログインボタン押下時に入力文字列をクリアするためにViewModelから制御

の処理を満たす必要があり、双方向バインディングが必要なため、DataBindingを適用しました。

### レイアウトファイルの設定
レイアウトXMLファイルとViewModelをDataBindingさせるため以下のように修正します。
#### 修正前
```fragment_login.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/login_fragment_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.fragment.LoginFragment">

    <RelativeLayout
        android:id="@+id/login_area"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal">
    </RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
```
#### 修正後
```fragment_login.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="loginViewModel"
            type="com.highcom.passwordmemo.ui.viewmodel.LoginViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/login_fragment_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.fragment.LoginFragment">

        <RelativeLayout
            android:id="@+id/login_area"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center_horizontal">
        </RelativeLayout>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
```
### 双方向バインディングのレイアウト設定
パスワード入力エリアの`androi:text`の要素に以下のように設定します。
```fragment_login.xml
                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/edit_master_password"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_centerHorizontal="true"
                    android:ems="10"
                    android:textSize="12sp"
                    android:text="@={loginViewModel.editMasterPassword}"
                    android:inputType="textPassword" />
```
`@={}`の書式で設定する事で、`editMasterPassword`の変数にレイアウトからの設定、ViewModelからの設定の双方に対応できます。

:::note info
ViewModelからの設定を画面に反映させたいだけの場合
`@{}`の書式になります。例として、パスワードの入力状況に応じて案内メッセージを変更する部分は以下のようになっています。
```fragment_login.xml
            <TextView
                android:id="@+id/navigate_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{loginViewModel.naviMessage}" />
```
:::

### ViewModelに変数の設定
パスワード入力エリアに入力された文字列を制御するための変数を以下のように定義する事で、レイアウトファイルから設定が出来るようになります。
```LoginViewModel.kt
class LoginViewModel : ViewModel() {
    /** 入力パスワード */
    val editMasterPassword = MutableStateFlow("")
    // etc...
}
```
### ViewModelから変数に設定
ログインボタン押下時に、入力されたパスワードをクリアするために、ログインボタン押下時に呼び出されるメソッド内で文字列をクリアする処理を以下のように実装します。
```LoginViewModel.kt
fun passwordLogin(context: Context, editPassword: String) {
    // etc...
    // 入力したパスワードはクリアする
    editMasterPassword.value = ""
}
```
この処理が呼び出されると、入力文字列がクリアされた状態で画面にも反映されます。

:::note info
ログインボタン押下時のイベントバインディング
以下のように`android:onClick`にラムダ式`@{()->method}`でメソッドを呼び出すことでイベントメソッドのバインディングができます。
```fragment_login.xml
    <Button
        android:id="@+id/login_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="@{() -> loginViewModel.passwordLogin(context, loginViewModel.editMasterPassword)}"
        android:text="@string/login" />
```
:::

:::note info
レイアウトViewを引数で渡せるイベントメソッドバインディング
`android:onClick`に設定するイベントメソッドは別の指定の方法もあり、以下のように`@{instance::method}`の形式で記載する事で、レイアウトのViewを引数に渡したメソッドをバインディングすることが出来ます。
```fragment_reference_password.xml
    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/edit_ref_url"
        android:inputType="text"
        android:text="@{fragment.passwordEditData.url}"
        android:onClick="@{fragment::onUrlParseTextClick}" />
```
```ReferencePasswordFragment.kt
    fun onUrlParseTextClick(view: View) {
        if (view is EditText) {
            if (view.text.toString() == "") return
            val uri = Uri.parse(view.text.toString())
            val intent = Intent(Intent.ACTION_VIEW, uri)
            val chooser = Intent.createChooser(intent, "選択")
            startActivity(chooser)
        }
    }
```
:::

# ViewBindingの使い方と使い所
### 使い所について
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545703/7dec1229-17c6-de29-1927-6240caef8ef5.png">

上記の設定画面のように、この画面で設定されたデータを`SharedPreference`に保存し、他の画面にて設定された状態に応じて表示内容を変える場合です。
そういった場合には、データの変更状態に応じてリアルタイムに画面へと反映させる必要が無いため、シンプルなバインディング方法であるViewBindingを利用します。

### レイアウトファイルの設定
レイアウトファイルに変更の必要はありません。
なので、例として生体認証ログインの有効/無効スイッチは以下のようなレイアウト設定のままです。
```fragment_setting.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    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"
    android:id="@+id/setting_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fastScrollEnabled="true">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Switch
            android:id="@+id/biometric_login_switch"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/login_biometrics_enable"
            android:checked="true" />
```

### FragmentでViewBindingの利用
ViewBindingをする事で、`findViewById`をする必要が無くなり、いきなり、上記の生体認証ボタンの有効/無効スイッチにアクセス出来るようになります。
ViewBindingの実装方法は以下です。
```SettingFragment.kt
class SettingFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentSettingBinding.inflate(inflater)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 生体認証ログインスイッチ処理
        val biometricLoginSwitch = binding.biometricLoginSwitch
        biometricLoginSwitch.setOnCheckedChangeListener { _, b ->
            // SharedPreferenceにデータを保存する
            loginDataManager?.setBiometricLoginSwitchEnable(b)
        }
    }
}
```
`oncreateView`の中で`FragmentSettingBinding`と言う名称でいきなり、`inflate`メソッドを呼び出す事が出来ます。

:::note info
命名規則が決まっており、XMLファイル名＋Bindingといった規則となるため、`fragment_setting.xml`という名称なら`FragmentSettingBinding`になります。
:::
その後、`onViewCreated`の中で、バインディング変数である`binding`から、`binding.biometricLoginSwitch`という変数で`Switch`レイアウトにアクセスする事が可能となり、リスナーメソッドの実装を記述する事が出来ます。

# さいごに
以上で、DataBindingとViewBindingの使い方と使い分けについての説明は以上となります。
最初は、すべてDataBindingで実装すればOKだと思って進めていたのですが、途中で、やろうとしていることはViewBindingで十分な部分もあるなと思い直したため、一部には簡単に実装ができるViewBindingを適用するようにしました。
なので、冒頭で紹介しているGitHubのプロジェクトでは、上記で紹介しているファイル以外にもDataBindingとViewBindingを使い分けて実装をしておりますので、ぜひ参考にして頂ければと思います。