LoginSignup
14
16

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-09-29

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

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

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

開発環境

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

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

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

できないこと

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

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

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

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

XXXBindingクラスが使えない

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

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

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>
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のラッパークラスを作成します。そうすれば問題ありません。

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から参照することで、見つからないというエラーを解消することができます。
ついでに自動生成されるクラスの名前が、 FragmentMainMainFragment ではなくて気持ち悪いところも解消できます。

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

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なども含む)を使う

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

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

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 を使用すると、

public class KotlinUserViewModel(): BaseObservable {

    // こまかいところは略

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

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

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

@BindingAdapterが使えない

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

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

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

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

パターン1.

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

パターン2.

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.

14
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
16