Help us understand the problem. What is going on with this article?

KotlinでAndroid開発するのに便利なライブラリ: KotterKnife

More than 5 years have passed since last update.

KotterKnifeは、AndroidのKotlinViewInjectionライブラリです。Java用のButterKnifeのKotlin版と言えます。開発者はButterKnifeと同じく我らが神Jake Whartonさんです。

前提知識

ViewInjectionライブラリ

Android開発で頻繁に登場するfindViewByIdなどをいい感じにやってくれるライブラリです。ButterKnifeの他にAndroidAnnotationsRoboGuiceなどがあります。

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)のコード例を示します。

before.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キーワードによりキャストしています。

after.kt
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が適切にビューを各プロパティ(nameEditTextsubmitButton)にバインドしてくれます。ビューがバインドされるタイミングは、そのプロパティへの最初のアクセスのときです。

解説

今回の例では、便宜的にプロパティとしてnameEditTextsubmitButtonを持っています。
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)については下記の資料を参照してください。
* Kotlin Null-Safety機構について
* KotlinのNullable型をモナドっぽくしてみた

おまけ: タネ明かし

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などのメソッドもあります。

ngsw_taro
自称Kotlinエバンジェリスト
https://taro.hatenablog.jp/
dr-ubie
病気予測AIによる病院向け問診サービス、及び to C 向け病気予測サービスを運営するスタートアップ
https://ubie.life/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした