KotterKnifeは、AndroidのKotlin用ViewInjectionライブラリです。Java用のButterKnifeのKotlin版と言えます。開発者はButterKnifeと同じく我らが神Jake Whartonさんです。
前提知識
ViewInjectionライブラリ
Android開発で頻繁に登場するfindViewById
などをいい感じにやってくれるライブラリです。ButterKnifeの他にAndroidAnnotationsやRoboGuiceなどがあります。
Kotlin
JetBrainsが中心に開発しているJVM言語です。Android開発にも対応しています。KotlinではButterKnifeやAndroidAnnotationsが使用できないのでViewInjectionライブラリの登場が待たれました。
KotlinでAndroidアプリ開発を始めるには@sys1yagiさんの「KotlinでAndroidアプリケーションを作ってみた2014初夏」が非常にわかりやすいです。
KotterKnife導入
導入は簡単です。下記のようにbuild.gradleを編集してください。
dependencies {
// (略)
compile 'com.jakewharton:kotterknife:0.1.0-SNAPSHOT'
}
repositories {
// (略)
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots/'
}
}
コード例
KotterKnifeの使用前(before.kt)と使用後(after.kt)のコード例を示します。
public class MainActivity : Activity() {
var nameEditText: EditText? = null
var submitButton: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
nameEditText = findViewById(R.id.name_edit_text) as EditText
submitButton = findViewById(R.id.submit_button) as Button
submitButton!!.setOnClickListener {
val name = nameEditText?.getText().toString()
Toast.makeText(this, name, Toast.LENGTH_SHORT).show()
}
}
}
before.ktでは通常通りfindViewById
でビューを取得し、as
キーワードによりキャストしています。
class MainActivity : Activity() {
val nameEditText: EditText by bindView(R.id.name_edit_text)
val submitButton: Button by bindView(R.id.submit_button)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
submitButton.setOnClickListener {
val name = nameEditText.getText().toString()
Toast.makeText(this, name, Toast.LENGTH_SHORT).show()
}
}
}
after.ktではfindViewById
の代わりにbindView
というKotterKnifeが提供する関数を使用しています。bindView
が適切にビューを各プロパティ(nameEditText
とsubmitButton
)にバインドしてくれます。ビューがバインドされるタイミングは、そのプロパティへの最初のアクセスのときです。
解説
今回の例では、便宜的にプロパティとしてnameEditText
とsubmitButton
を持っています。
Kotlinではvar
またはval
キーワードを使用して変数(ここではプロパティ)を宣言できますが、var
は書き換え可能な変数でval
は読み取り専用の変数です。原則としてval
を使用することが望ましいのですが、プロパティの宣言時にはビューを取得できないため、before.ktではプロパティをvar
として宣言し、一時的にnull
をセットして後でビューに書き換えています。
一方after.ktではby bindView(resId)
のおかげでプロパティをval
で宣言できます。bindView
の使用者はキャストをする必要がなく、また型推論が働くために型指定の重複を避けられます。次のコードはすべて正しくコンパイル、実行されます。
// プロパティ
// 型の省略なし
val a: Button by bindView<Button>(R.id.submit_button)
// bindViewの型パラメータを省略
val b: Button by bindView(R.id.submit_button)
// 変数の型アノテーションを省略
val c by bindView<Button>(R.id.submit_button)
Kotlinではnull
を代入できる変数と、代入できない変数を型として表現します(便宜的にnull
を代入できる型をNullable型、代入できない型をNotNull型と呼ぶことにします)。Kotlinでは基本的にデフォルトですべてNotNull型です。Nullable型は特別扱いされます。NPE(NullPointerException)を起こさせないための工夫です。そのためNullable型を扱うことは安全ですが、コーディングの上で煩わしく感じることもあります。
before.ktでは、各プロパティはNullable型です。プロパティ宣言時にはビューを取得できないのでnull
をセットせざるを得ません[1]。after.ktの各プロパティはNotNull型です。その代わりにbindView
はビューが見つからない場合などに例外を投げます[2]。ビューが見つからない場合などはたいていプログラマのミスなのでバインド時に例外で知らせてくれるのはいいアプローチだと思います。
KotlinのNullable型を特別扱いしてNPEを起こさせないための仕組み(Null-safety)については下記の資料を参照してください。
おまけ: タネ明かし
KotterKnifeが使用しているKotlinの言語機能はDelegated Propertyと呼ばれるものです。これはプロパティへのアクセスを他のオブジェクトに委譲する仕組みです。
class MyClass {
var myProperty1: String = "普通のプロパティ"
var myProperty2: String by 委譲先のオブジェクト
}
「委譲先のオブジェクト」は、例えば次のようなクラスのインスタンスなどです。
class Decorator {
private var value: String? = null
// プロパティの値の参照が、このメソッドに委譲されます
fun get(thisRef: Any, prop: PropertyMetadata): String {
return "${prop.name}の値は${value}だよ♪"
}
// プロパティの値の変更が、このメソッドに委譲されます
fun set(thisRef: Any, prop: PropertyMetadata, value: String) {
this.value = "☆☆☆${value}☆☆☆"
}
}
class Person {
var name: String by Decorator()
}
fun main(args: Array<String>) {
val taro = Person()
taro.name = "Taro"
println(taro.name) // => nameの値は☆☆☆Taro☆☆☆だよ♪
}
詳細はKotlin公式サイトか、「KotlinでAndroidアプリ開発: Viewのインジェクションを実現する」を参照してください。
まとめ
KotterKnifeはViewInjectionライブラリで、KotlinでのAndroidアプリ開発におけるボイラープレートを減らしてくれます。それを実現しているのはKotlinのDelegeted Propertyという機能です。
現時点でKotterKnifeのプロダクトコードはたったの99行なので、興味のある方は読んでみてください。
--
[1] 後述のDelegated Propertyを使用すると、宣言時に値が確定していないプロパティをNotNull型にできます。
[2] 例外を投げずにnull
を返すbindOptionalView
などのメソッドもあります。