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
メソッドを使う方法です。
具体的には以下のような実装になります。
<LinerLayout android:id="@+id/root_layout">
<Button android:id="@+id/submit_button" />
</LinerLayout>
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が変数に代入されたり)してしまいます。
開発する上では注意が必要です。
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
の登場により、とうとう開発は終了する方針になりました。
Attention: Development on this tool is winding down. Please consider switching to view binding in the coming months.
今から新規でアプリを作る際にButterKnifeを採用することはないと思いますが、かなりの利用実績はあると思います。
Butter Knifeでは以下のように、アノテーションを使って、バインドするViewのIDを指定します。
Viewだけでなくクリックのメソッドやリソースなどもバインドすることが可能です。
詳細はドキュメントをご参照ください。
<LinerLayout android:id="@+id/root_layout">
<Button android:id="@+id/submit_button" />
</LinerLayout>
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の指定を間違えるとクラッシュなどに繋がりますので、こちらについても注意が必要です。
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でメンバ変数のように扱うことができます。
<LinerLayout android:id="@+id/root_layout">
<Button android:id="@+id/submit_button" />
</LinerLayout>
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などが発生することがあるので注意しましょう。
<LinerLayout>
<Button android:id="@+id/confirm_button" />
</LinerLayout>
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
を用いたサンプルのコードになります。
data class SampleBindingData(
val text: String
)
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>
<data>
<variable
name="sample"
type="jp.sadashi.sample.viewbind.SampleBindingData" />
</data>
<LinerLayout>
<TextView android:text="@{sample/text}">
</LinerLayout>
</layout>
また、イベント処理についても同様にバインドすることができます。
class MainPresenter() {
fun submit(view: View) {
// do something
println("Click $view")
}
}
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>
<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へのアクセスに特化したものになっています。
詳しくは公式ドキュメントをご参照ください。
使い方のサンプルコードは以下の通りです。
生成されたBindingクラスには必ずroot
というViewがあるのでそれをActivityならsetContentViewすればOKです!
<LinerLayout>
<Button android:id="@+id/submit_button" />
</LinerLayout>
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のスライドで紹介されていた表がこちらです。
個人的な見解をまとめます。
- Kotlin Android Extensions
- 記述のしやすさが良い
- 厳格な型の安全性をそこまで求めないなら十分
- View Binding
- 型の安全性を求めるならこれ
- ただし、まだAndroid Studio 3.6はベータ版
- Data Binding
- データのバインディングまで利用するなら機能的にもこれ一択
さいごに
最後までお読みいただきありがとうございました!
明日は@azawakhさんによる「Node.js 13.2.0 で--experimental-modules外れたのでESMを試してみた」です。お楽しみに!