35
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

食べログAdvent Calendar 2019

Day 19

Android View Bind Library 比較

Last updated at Posted at 2019-12-18

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

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

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

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

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

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

findViewById

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

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

layout/activity_main.xml
<LinerLayout android:id="@+id/root_layout">
    <Button android:id="@+id/submit_button" />
</LinerLayout>
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が変数に代入されたり)してしまいます。
開発する上では注意が必要です。

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です。
かなりお世話になっている or いた方も多いのではないかと思いますが、後述するView Bindingの登場により、とうとう開発は終了する方針になりました。

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だけでなくクリックのメソッドやリソースなどもバインドすることが可能です。
詳細はドキュメントをご参照ください。

layout/activity_main.xml
<LinerLayout android:id="@+id/root_layout">
    <Button android:id="@+id/submit_button" />
</LinerLayout>
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の指定を間違えるとクラッシュなどに繋がりますので、こちらについても注意が必要です。

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です。
こちらは利用することで、レイアウトファイルで指定したIDでメンバ変数のように扱うことができます。

layout/activity_main.xml
<LinerLayout android:id="@+id/root_layout">
    <Button android:id="@+id/submit_button" />
</LinerLayout>
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などが発生することがあるので注意しましょう。

layout/activity_sub.xml
<LinerLayout>
    <Button android:id="@+id/confirm_button" />
</LinerLayout>
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です。詳細は公式ドキュメントを見ていただくのが一番いいとは思いますが、簡単な使い方から紹介したいと思います。

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

<data>要素にはこのレイアウトにバインドするクラスを記述し、データをどのように反映するかは@{}構文を使用します。構文の詳細はLayouts and binding expressionsを参考にしてください。

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

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

SampleBindingData.kt
data class SampleBindingData(
    val text: String
)
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")
    }
}
layout/activity_main.xml
<layout>
    <data>
        <variable
            name="sample"
            type="jp.sadashi.sample.viewbind.SampleBindingData" />
    </data>
    <LinerLayout>
        <TextView android:text="@{sample/text}">
    </LinerLayout>
</layout>

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

MainPresenter.kt
class MainPresenter() {
    fun submit(view: View) {
        // do something
        println("Click $view")
    }
}
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()
    }
}
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を利用すれば、ボイラープレートコードも少なく、型も安全に使えます。
また、LiveDataViewModelといったJetPackの機能を利用したMVVMアーキテクチャでの実装を考えているのでしたら、こちらを利用するのが良いかと思います。
懸念としてはこちらもAnnotation Proessingを利用しているため、ビルド速度が遅くなるという点があります。

View Binding

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

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

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

layout/activity_main.xml
<LinerLayout>
    <Button android:id="@+id/submit_button" />
</LinerLayout>
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

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

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

さいごに

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

35
13
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
35
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?