KotlinとDataBindingでできることできないこと


5月に発表されてAndroidのDataBindingですが、9月になり本格的にアプリの開発でも使用するようになりました。
その際Kotlinを使用していたのですが、Javaの場合は問題ないけど、Kotlinで書いたら問題があるという点がありましのたでまとめておきます。

DataBindingの基本的な使い方は公式リファレンスを参照。

* [Databinding](https://developer.android.com/intl/ja/tools/data-binding/guide.html)

以下のできないことと未確認の `@BindingConversion` 以外はKotlinからもできることを確認できています。

#### 開発環境

開発環境は次のようになります。
Databindingでは内部Kotlinを使用しているため、アプリで使用するKotlinのバージョンを合わせています。

|ライブラリ|バージョン|
|--------|--------|
|DataBinding|v1.0-rc2|
|Kotlin|v0.13.1513|

※ 9/15 に v1.0-rc2 がリリースされました！

## できないこと

以下のコードは完全版がGitHubにあります。

* [GitHub](https://github.com/droibit/databinding.kotlin)

### 自動生成されるファイルが扱えない

Kotlinを使用していてできない部分で大きいのが、 **自動生成されるファイルが扱えない** 点です。

レイアウトを編集するとDataBindngのプラグインにより自動的にソースコードが生成されますが、それを使おうとした場合、ビルド時にクラスなどが見つからないといわれてしまいます。

#### XXXBindingクラスが使えない

DataBindingではlayout.xmlクラスから対応するクラスが自動生成されます。 
その `XXXBinding` クラスをKotlinで直接扱うことができません。

例えば、次のようなモデルとレイアウトがあった場合、

``` xml:fragment_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
	<data>
		<!-- バインディングするクラス -->
		<variable name="viewModel" type="com.droibit.databinding.kotlin.KotlinUserViewModel" />
	</data>
	<!-- 実装は省略 -->
</layout>
```

``` kotlin:KotlinUserViewModel.kt
public class KotlinUserViewModel(firstName: String = "花子", lastName: String = "山田") {
    public val firstName = ObservableField<String>(firstName)
    public val lastName = ObservableField<String>(lastName)

	public val firstNameWatcher: TextWatcher = ...
	public val lastNameWatcher: TextWatcher = ...
}
```

対応するクラスは `FragmentMainBinding`  になります。

このクラスが使用できないのは致命的です（モデルとのバインディングができない為）。Kotlinで直接参照するのではなく、Javaのラッパークラスを作成します。そうすれば問題ありません。

``` java:MainFragmentBindingAdapter.java
public MainFragmentBindingAdapter {

	private final FragmentMainBinding mBinding;

	// フラグメントで使用する場合の書き方
    public MainFragmentBindingAdapter(LayoutInflater inflater, ViewGroup container) {
        mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false);
    }

   public KotlinUserViewModel getViewModel() {
        return mBinding.getViewModel();
    }

    public void setViewModel(@NonNull KotlinUserViewModel viewModel) {
        mBinding.setViewModel(viewModel);
    }

    public View getRoot() {
        return mBinding.getRoot();
    }
}
```

手動でラッパーを書かないといけないので少々面倒ですが、このクラスをKotlinから参照することで、見つからないというエラーを解消することができます。
ついでに自動生成されるクラスの名前が、 `FragmentMain` で `MainFragment` ではなくて気持ち悪いところも解消できます。

ラッパークラスを使用するときはこのようになります。

``` kotlin:MainFragment.kt
public class MainActivityFragment : Fragment() {

    private var mBinding: MainFragmentBindingAdapter by Delegates.notNull()
    private val mViewModel = KotlinUserViewModel()

    /** {@inheritDoc} */
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        mBinding = MainFragmentBindingAdapter(inflater, container)
        mBinding.viewModel = mViewModel
        return mBinding.root
    }
}
```

#### BaseObservableが間接的に使えない

ビューとモデルをバインディングする際のモデルを作成する方法は主に2パターンあります。

* BaseObservableを使う
* ObservableFields(Intなども含む)を使う

クラス単位か、フィールド単位かのような違いです。

* [ObservableObjects](https://developer.android.com/intl/ja/tools/data-binding/guide.html#observable_objects)
* [ObservableFields](https://developer.android.com/intl/ja/tools/data-binding/guide.html#observablefields)

この内、BaseObservableを使用したバインディングはKotlinを使用している場合使えません。
これも自動生成されるファイルを直接扱うことになるためです。

``` xml:fragment_main.xml
<data>
	<variable name="viewModel" type="com.droibit.databinding.kotlin.KotlinUserViewModel" />
</data>
<!-- 略 -->
<TextView
	android:id="@+id/last_name"
    android:text="@{viewModel.lastName}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="@dimen/abc_text_size_medium_material" />
```

上記のようにTextViewとモデルをバインディングし、モデルが変更されたらビューも自動的に更新されるようにしたい場合、 BaseObservable を使用すると、

``` kotlin
public class KotlinUserViewModel(): BaseObservable {

	// こまかいところは略

	fun setLastName(name: String) {
		this.firstName = firstName;
        notifyPropertyChanged(BR.lastName);  // <- ココ！
	}
}
```

セッターの中で、`#notifyPropertyChanged(int)` を呼ばなければいけません。
しかし、このメソッドに渡している **BR.lastName** も自動生成されたものになるので、ビルド時に見つからないといわれてしまいます。

なのでモデルは、 **ObservableFields** を使用しましょう。

### @BindingAdapterが使えない

DataBindingではレイアウトで使用できる独自セッターを作成することができます。

* [Attribute Setters](https://developer.android.com/intl/ja/tools/data-binding/guide.html#attribute_setters)

カスタムセッターを作成する際は、まずコード側で適当なクラスにスタティックメソッドを定義し、`@BindingAdapter("app:bindHoge")` アノテーションを設定します。そして、レイアウト側で使用します。

Kotlinでこのセッターを書いた場合、ビルド時にそんな属性はないと言われてしまうため、Javaで書く必要があります。

Kotlinで EditTextに `TextWatcher` を設定するカスタムセッターを書かいてみた場合、

パターン1.

```kotlin:KotlinBindingAdapters.kt
public object BindingAdapters {
    @BindingAdapter("app:onChange")
    public fun bindTextWatcher(view: EditText, watcher: TextWatcher) {
        view.addTextChangedListener(watcher)
    }	
}
```

パターン2.

```kotlin:
public class KotlinBindingAdapters {

    companion object {
        @BindingAdapter("app:onChange")
        public fun bindTextWatcher(view: EditText, watcher: TextWatcher) {
            view.addTextChangedListener(watcher)
        }
    }
}
```

どちらのパターンもダメでした…。

Kotlinで全て書くことができればいいのですが、v1.0-rc2の時点ではまだJavaを併用しないといけないので少々面倒ですが、それ以上にバインディングによるビューの更新は便利だと感じているので今後に期待です。

### その他注意点

コード以外にもDataBindingが使用しているKotlinのバージョンと、アプリで使用するもののバージョンは合わせ置いたほうが無難です。最悪ビルドが通らなくなります。DataBinding v1.0-rc2とM13のリリースがほぼ同じだったので、今後も追従してくれるといいのですが。

> Written with [StackEdit](https://stackedit.io/).