Android View Bind Library 比較

AndroidにおいてViewをBindする方法はいくつかあります。
今回は、その手法の紹介および比較をしたいと思います。

この比較の話はGoogle I/O 2019で紹介されていたものです。
https://www.youtube.com/watch?v=Qxj2eBmXLHg

その話を各手法を具体的に紹介しつつ、実際にどういったところに良し悪しがあるのかを説明したいと思います。

各サンプルコードは一部抜粋したものになっていますのでご了承ください。
実際に動くコードはGitHubに上げていますのでそちらをご参照ください。

https://github.com/sadashi-ota/AndroidViewBindSample

前提の環境は以下の通りです

- Android Studio 3.6 Beta 5
- Kotlin 1.3.50
- その他ライブラリについては各種Branchに上がってる`app/build.gradle`をご参照ください

# findViewById

入門書にも乗ってる一番基本的な方法で、ActivityやViewにある`findViewById`メソッドを使う方法です。

具体的には以下のような実装になります。

```xml:layout/activity_main.xml
<LinerLayout android:id="@+id/root_layout">
    <Button android:id="@+id/submit_button" />
</LinerLayout>
```

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    lateinit var button: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button = findViewById(R.id.submit_button)
        button.setOnClickListener(::submit)
    }

    private fun submit(view: View) {
        // do something
        println("Click $view")
    }
}
```

ただし、もし以下のように`findViewById`で渡すIDを別のViewのものを渡してしまうと、クラッシュしてしまったり、意図しない挙動で動いてしまったり（別のViewが変数に代入されたり）してしまいます。
開発する上では注意が必要です。

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    lateinit var button: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // ↓ LinerLayoutをButtonに代入しようとするので、ClassCastExceptionがスローされる
        button = findViewById(R.id.root_layout)
    }
}
```

この方法は、ビルド速度的には特に懸念はありませんが、ボイラープレートコードが増え、取得できるViewの型もわからないため、型の安全性にも欠けるので、オススメはできません。

# Butter Knife

次は[Butter Knife](https://github.com/JakeWharton/butterknife)です。
かなりお世話になっている or いた方も多いのではないかと思いますが、後述する`View Binding`の登場により、とうとう開発は終了する方針になりました。

```markdown:README.md
Attention: Development on this tool is winding down. Please consider switching to view binding in the coming months.
```

今から新規でアプリを作る際にButterKnifeを採用することはないと思いますが、かなりの利用実績はあると思います。

Butter Knifeでは以下のように、アノテーションを使って、バインドするViewのIDを指定します。
Viewだけでなくクリックのメソッドやリソースなどもバインドすることが可能です。
詳細は[ドキュメント](https://jakewharton.github.io/butterknife/)をご参照ください。

```xml:layout/activity_main.xml
<LinerLayout android:id="@+id/root_layout">
    <Button android:id="@+id/submit_button" />
</LinerLayout>
```

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    @BindView(R.id.submit_button)
    lateinit var button: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ButterKnife.bind(this)
        println("Button is $button")
    }

    @OnClick(R.id.submit_button)
    fun submit(view: View) {
        // do something
        println("Click $view")
    }
}
```

ただし、こちらについても`findViewById`と同じようにIDの指定を間違えるとクラッシュなどに繋がりますので、こちらについても注意が必要です。

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    @BindView(R.id.root_layout) // ← IDが違う
    lateinit var button: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ButterKnife.bind(this) // ← 呼び出してバインドする際にクラッシュする
    }
}
```

`findViewById`に比べるとかなりボイラープレートコードが少なくなるため、重宝された方も多いかと思います。また、`Reflection`も利用していないので、実行時のパフォーマンスの面でも特に問題はありません。
しかし、こちらも型の安全性であったり、`Annotation Proessing`を利用しているためビルド時間が長くなったり、といった懸念があります。

# Kotlin Android Extensions

続いて[Kotlin Android Extensions](https://kotlinlang.org/docs/tutorials/android-plugin.html#view-binding)です。
こちらは利用することで、レイアウトファイルで指定したIDでメンバ変数のように扱うことができます。

```xml:layout/activity_main.xml
<LinerLayout android:id="@+id/root_layout">
    <Button android:id="@+id/submit_button" />
</LinerLayout>
```

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        submit_button.setOnClickListener(::submit)
        println("Button is $submit_button")
    }

    private fun submit(view: View) {
        // do something
        println("Click $view")
    }
}
```

こちらの場合は、他のIDを指定してしまった時も型が違うのでビルド時にある程度ミスに気づけます。
ただし、別のViewのIDを使ってしまった時はViewが見つからないため、実行時にNullPointerExceptionなどが発生することがあるので注意しましょう。

```xml:layout/activity_sub.xml
<LinerLayout>
    <Button android:id="@+id/confirm_button" />
</LinerLayout>
```

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // confirm_buttonは別のレイアウトのIDなのでNullPointerExceptionが発生する
        confirm_button.setOnClickListener(::submit)
    }

    private fun submit(view: View) {
        // do something
        println("Click $view")
    }
}
```

`Kotlin Android Extensions`は生成されるJavaコードを見ればわかりますが`findViewById`のキャッシュを実装したシンプルなものになっています。
ボイラープレートコードもより少なく、ビルド速度も懸念はありません。
また、型の安全性についてもを`findViewById`を直接扱うよりも安全に扱えるため、かなり使いやすいと思います。

# Data Binding

次は`Data Binding`です。詳細は[公式ドキュメント](https://developer.android.com/topic/libraries/data-binding)を見ていただくのが一番いいとは思いますが、簡単な使い方から紹介したいと思います。

`Data Binding`は今まで紹介した手法とは異なり、`layout`のXMLファイルでバインドを行います。
まずはレイアウトファイルのルートタグを`<layout>`タグで始めます。その後、`<data>`要素と通常の`View`の要素を宣言します。

`<data>`要素にはこのレイアウトにバインドするクラスを記述し、データをどのように反映するかは`@{}`構文を使用します。構文の詳細は[Layouts and binding expressions](https://developer.android.com/topic/libraries/data-binding/expressions)を参考にしてください。

レイアウトファイルを作成すると、バインディングクラスが生成されます。
生成されるクラス名は`activity_main.xml`から生成する場合は`ActivityMainBinding`となります。
`Binding`クラスの生成については、生成された`Binding`クラスにある`inflate`メソッドか、`DataBindingUtil`を利用しましょう。

以下、`DataBindingUtil`を用いたサンプルのコードになります。

```kotlin:SampleBindingData.kt
data class SampleBindingData(
    val text: String
)
```

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.sample = SampleBindingData("Sample")
    }
}
```

```xml:layout/activity_main.xml
<layout>
    <data>
        <variable
            name="sample"
            type="jp.sadashi.sample.viewbind.SampleBindingData" />
    </data>
    <LinerLayout>
        <TextView android:text="@{sample/text}">
    </LinerLayout>
</layout>
```

また、イベント処理についても同様にバインドすることができます。

```kotlin:MainPresenter.kt
class MainPresenter() {
    fun submit(view: View) {
        // do something
        println("Click $view")
    }
}
```

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.presenter = MainPresenter()
    }
}
```

```xml:layout/activity_main.xml
<layout>
    <data>
        <variable
            name="presenter"
            type="jp.sadashi.sample.viewbind.MainPresenter" />
    </data>
    <LinerLayout>
        <Button android:onClick="@{presenter::submit}">
    </LinerLayout>
</layout>
```

`Data Binding`を利用すれば、ボイラープレートコードも少なく、型も安全に使えます。
また、`LiveData`や`ViewModel`といった`JetPack`の機能を利用したMVVMアーキテクチャでの実装を考えているのでしたら、こちらを利用するのが良いかと思います。
懸念としてはこちらも`Annotation Proessing`を利用しているため、ビルド速度が遅くなるという点があります。

# View Binding

最後にViewBindingの紹介です。
こちらはAndroid Studio 3.6 Canary 11 以降から使える機能で、DataBindingと同様にレイアウトファイルごとにBindingクラスが生成されます。
Bindingクラスにはレイアウトファイルで設定したIDヘの直接参照が取り込まれていて、DataBindingのViewへのアクセスに特化したものになっています。
詳しくは[公式ドキュメント](https://developer.android.com/topic/libraries/view-binding)をご参照ください。

使い方のサンプルコードは以下の通りです。

生成されたBindingクラスには必ず`root`というViewがあるのでそれをActivityならsetContentViewすればOKです！

```xml:layout/activity_main.xml
<LinerLayout>
    <Button android:id="@+id/submit_button" />
</LinerLayout>
```

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.submitButton.setOnClickListener(::submit)
    }

    private fun submit(view: View) {
        // do something
        println("Click $view")
    }
}
```

`View Binding`を利用することで、ボイラープレートコードも減り、型の安全性も担保できます。
また、ビルド速度についても`Annotation Proessing`を利用していないため、十分だと思われます。
`Data Binding`と異なり、`<layout>`タグも用いる必要がないので、導入もしやすいかと思います。

# 比較

Google I/O 2019のスライドで紹介されていた表がこちらです。
![how_to_access_view.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189865/9e09598e-c90f-7b7e-daa2-00f72f1d0153.png)

個人的な見解をまとめます。

* Kotlin Android Extensions
    * 記述のしやすさが良い
    * 厳格な型の安全性をそこまで求めないなら十分
* View Binding
    * 型の安全性を求めるならこれ
    * ただし、まだAndroid Studio 3.6はベータ版
* Data Binding
    * データのバインディングまで利用するなら機能的にもこれ一択

# さいごに

最後までお読みいただきありがとうございました！
明日は@azawakhさんによる「Node.js 13.2.0 で--experimental-modules外れたのでESMを試してみた」です。お楽しみに！