Kotlin×Data Bindingの問題点と解決法

  • 31
    いいね
  • 0
    コメント

はじめに

この記事はKotlin Advent Calendar 2016の3日目の記事です。

KotlinとData Binding、両方とも便利ですよね。この記事ではこれらを組み合わせて使った場合に発生するいくつかの問題点と解決法を紹介したいと思います。ググるといくつか情報が出てきますが、断片的であったり、新しい解決法が登場していたりするため、情報を集約して解決法を列挙する形で書いています。

この記事では、
1. Kotlinから自動生成されたクラスが参照できない
2. BindingAdapterを使ったカスタムバインディングが使えない
という2つの問題を扱います。

1. Kotlinから自動生成されたクラスが参照できない

これはData Bindingに限った話ではなく、KotlinとAnnotation Processingを組み合わせた場合に発生します。

Data Bindingライブラリを使う場合、xmlで記述したレイアウトをlayoutタグで囲むことで、FooBindingのようなクラスが生成されます。しかし、このFooBindingをKotlinから参照すると正常にビルドが出来ず、以下のようなビルドエラーが表示されると思います。

Error:(7, 35) Unresolved reference: databinding
Error:(19, 27) Unresolved reference: ItemArticleBinding
Error:(40, 35) Unresolved reference: ItemArticleBinding

この記事で詳しくは説明しませんが、KotlinとJavaのコンパイル順序が関係しています。以下の記事で詳しい解説があるので、興味のある方はご覧ください。

この問題を解決する方法として、2016年12月現在では2つの方法があります。

generateStubsを有効にする

build.gradleに以下の記述を追加することでKotlinとJavaのコンパイル順序に関連した問題が解消されて、KotlinからFooBindingのような自動生成されたクラスが参照できるようになると思います。

kapt {
    generateStubs = true
}

kotlin-kaptを使う

もう1つの解決方法として、同じくbuild.gradleに以下の記述を追加することも解決できます。

apply plugin: 'kotlin-kapt'

しかし、公式ブログには以下のような記述があり、kotlin-kaptはまだ実験段階のようです。この記事を書くにあたって簡単なサンプルを実装した限りは正常に動作しましたが、まだ問題が残っている可能性もありそうです。

The new annotation processing still has known issues and may not be compatible with all annotation processors. You should enable it only if you’ve run into problems with the default kapt annotation processing implementation.

2. BindingAdapterを使ったカスタムバインディングが使えない

Data BindingではTextViewにStringをバインドするといった標準で用意されているバインディングに加えて、カスタムバインディングを実装することができます。よくある例としては、ImageViewにGlideやPicassoなどを使ってURLをバインディングするものだと思います。これをJavaで実装すると以下のようになります。

public class CustomBinder {
    @BindingAdapter("app:imageUrl")
    public static void imageUrl(ImageView imageView, String url) {
        Glide.with(imageView.getContext()).load(url).into(imageView);
    }
}

JavaではBindingAdapterアノテーションを付与したstaticなメソッドとしてカスタムバインディングを実装することができます。これをKotlinで実装すると以下のようになります。

object CustomBinder {
    @BindingAdapter("app:imageUrl")
    fun imageUrl(imageView: ImageView, url: String?) {
        Glide.with(imageView.context).load(url).into(imageView)
    }
}

これでいけるかと思いきや、実行すると以下のようなエラーが発生します。

java.lang.IllegalStateException: Required DataBindingComponent is null in class ItemArticleBinding. A BindingAdapter in com.yuyakaido.android.flow.presentation.binder.CustomBinder.Companion is not static and requires an object to use, retrieved from the DataBindingComponent. If you don't use an inflation method taking a DataBindingComponent, use DataBindingUtil.setDefaultComponent or make all BindingAdapter methods static.

一言で要約すると、BindingAdapterはstaticメソッドとして実装してね、という感じです。

この問題を解決する方法はいくつも考えられますが、大きくは2つあります。

JvmStaticアノテーションを付与する

Kotlinにはstaticというキーワードはありませんが、以下のようにJvmStaticアノテーションを付与すると、Javaから見たときにstaticとして見えるようになります。

object CustomBinder {
    @JvmStatic
    @BindingAdapter("app:imageUrl")
    fun imageUrl(imageView: ImageView, url: String?) {
        Glide.with(imageView.context).load(url).into(imageView)
    }
}

これでめでたくカスタムバインディングが動作するはずです。

拡張関数としてカスタムバインディングを実装する

もっとKotlinらしく書くとしたら、拡張関数としてカスタムバインディングを実装してもいいかもしれません。

@BindingAdapter("android:imageUrl")
fun ImageView.imageUrl(url: String?) {
    Glide.with(context).load(url).into(this)
}

上記のようにImageViewクラスにimageUrlという拡張関数を実装すると、以下のようにカスタムバインディングであることを意識せずにバインドできるようになります。

<ImageView
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:imageUrl="@{article.thumbnail()}"/>

さいごに

この記事ではKotlinとData Bindingを組み合わせて使う場合の問題点と解決策を紹介しました。

  • Kotlinから自動生成されたクラスが参照できない
    • generateStubsを有効にする
    • kotlin-kaptを使う
  • BindingAdapterを使ったカスタムバインディングが使えない
    • JvmStaticアノテーションを付与する
    • 拡張関数としてカスタムバインディングを実装する

また、この記事で紹介したコードは以下のリポジトリで公開しています。興味がある方は覗いてみてください。

それでは、良いKotlin×Data Bindingライフを!

この投稿は Kotlin Advent Calendar 20163日目の記事です。