woshidan's blog

そんなことよりコードにダイブ。

「DataBindingのコードを読む」のスライドが面白かったのでDataBindingについて少しまとめました

speakerdeck.com

上のスライドが面白かったため、DataBindingについてスライドを読んでわかったことや追加で調べたことをメモしました。

時間が足りず、書いて動かしたわけじゃないし雑なのですが、自分用のメモとしてはないよりましでしょう*1

Androidアプリケーション用Gradle Plugin(Gradle 3.0.0)にてData Binding関連で含まれているもの

  • Data Bindingライブラリの依存を自動で追加するルール*2
  • Data Binding用の処理で使うと思しきDataBindigInfo クラスとそのクラスにアノテーションを利用してデータを突っ込むdataBindingExportBuildInfoタスクの追加
  • ビルドプロセスの中で、リソースファイルマージのタイミングでレイアウトリソースから関連パラメータを読み取ってDataBinding(のアノテーションプロセッサ?)で利用しやすいように加工したxmlファイルを用意*3

依存ライブラリの追加について(追加で調べたこと)

  • build.gradleに対応するプロパティを設定しておくと、DataBindingに関連するライブラリの依存を自動的に追加するルールがAndroid用Gradleに定義されそう
  • Androidプロジェクトのビルドなどのタスクを実行する前後*4に実行されるものとして定義されているみたい
  • BaseComponentModelPlugin.java というファイルの中にAndroidアプリケーションプロジェクトで使う build.gradle で使うようなタスクやAndroid アプリケーションプラグインに入っている標準的なConfiguration*5やデフォルトタスクやルールが定義されている*6
  • @Mutate はbuild.gradleで設定されたビルド対象(のなにか)を引数に取り、ビルド対象に変化を与えるためのRule*7っぽい
    • build.gradleの android.dataBinding.enabled を見て実際ルールの中身が動くかどうか決めていそう*8
    • 動く順序として、@Mutate で設定されたルールはデフォルトルールの後に動く
// https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/BaseComponentModelPlugin.java#619
        @Mutate
        public void addDataBindingDependenciesIfNecessary(
                TaskManager taskManager,
                @Path("android.dataBinding") DataBindingOptions dataBindingOptions) {
            taskManager.addDataBindingDependenciesIfNecessary(
                    new DataBindingOptionsAdapter(dataBindingOptions));
        }
// https://developer.android.com/topic/libraries/data-binding/index.html
// build.gradle
android {
    ....
    dataBinding {
        enabled = true
    }
}

DataBindingに関係しているクラスについて

ViewDataBindingクラス

  • layout_xxx から始まるレイアウトリソースから自動生成されるっぽい
  • ViewModelからの変更通知を受け取って、それをViewBindingAdapterに通知(on executeBindings() , executeBindings()が呼び出されるきっかけはChreographer.postFrameCallback())
  • クラスとしてはViewDataBindingのサブクラス。レイアウトリソースと対応して自動生成される
    • 他のViewBindingAdapterクラスと関わる部分(イベントリスナやexecuteBindings()の中身?)もViewBindingAdapterクラスのアノテーションから生成っぽい
  • 対応するViewModelと自動生成されたIDを(でかい何かに)登録する
    • DataBindingの(でかい何かに)更新しろ~と呼び出されるにはIDが必要で、BR.javaで管理されている?*9

ViewBindingAdapterクラス*10

  • EventListenerを書いたり、Viewの更新コード*11を書いたり(Viewへの反映を呼び出すのは ViewDataBinding.executeBindings())
    • EventListenerに近いが、Viewの特定の属性の変化の監視、といったことができる
      • @BindingAdapter("android:text") のように監視したい属性を書き、監視した属性が変化した時のコールバックをstaticメソッドで定義*12
    • なお、EventListenerの定義はViewBindingAdapterにアノテーションで書くとアノテーションプロセッサによりViewDataBindingに生える?
  • 自分が管理してるViewが持ってる値を取得することもでき*13、それをViewModelに渡したり*14

ViewModelクラス

  • BaseObservableのサブクラス。
  • ViewDataBindingにセットして使う。ViewDataBindingでどのViewModelを使うか、といったことは開発者側で定義するし、自動生成されないのでアノテーションつけながら自分で書く
  • DataBindingで使いたい属性のgetterには@Bindableをつけておくと、その属性のIDがBRクラスに生えるっぽい
  • ModelはViewModelを知ってて、Repositoryとかの方からModelに変更通知 => ModelがViewModel持ってたらViewModelに通知の流れ?

DataBindingComponentクラス

  • getXxxBindingAdapter()メソッドで対応するViewDataBindingクラスの初期化を定義
  • DataBindingComponentのインスタンスDataBindingUtil.setContentView() に渡すことで、たぶん getXxxBindingAdapter()メソッドが呼び出されてViewDataBindingのインスタンスが生成
  • mDirtyFlagsで工夫してそうですが、DataBindingの実装見てたら非同期処理がバリバリ入るので、中途半端な状態のViewDataBindingのインスタンスの参照をDataBinding関連の他のコンポーネントに公開したくない。ので、完全にインスタンスの生成が済んだオブジェクトをreturnするメソッドを定義させることを関連クラスの定義としておくことで、その辺のお作法を強制してるように見えましたが、どうなんでしょうね。

*1:ツッコミお待ちしております。。

*2:http://mike-neck.hatenadiary.com/entry/2015/05/18/220834 こちらの記事によると、Gradle2.4からルールという概念が登場したそうですが、4.5.1でもincubatingです

*3:リリース用にビルドをしていく際も公開するapkなどのファイルに設定ファイルは含まれないはずですが、アノテーションを使ってリリース時の最新コードを生成するためにこの設定ファイルが必要だから、このパースはmergeReleaseResourceでも実行されそうな気がしましたが確認してません

*4:前後ってなんだよって感じですが、はい

*5:compileやprovidedなど

*6:https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-experimental/src/main/java/com/android/build/gradle/model/BaseComponentModelPlugin.java#619

*7:Ruleというものを使ってGradleのドメインモデルを使った新しい書き方をやろう、的な話があり、 @Mutate はそのRuleの一種を示すアノテーション。DefaultRuleの後に動くものらしいのですが、GradleのAPI ドキュメントが4.5.1しかなくてバージョン違いすぎでよくわからず。。 https://docs.gradle.org/current/javadoc/org/gradle/model/Mutate.html

*8:https://developer.android.com/topic/libraries/data-binding/index.html

*9:Data Binding版R.javaみたいですね

*10:BindingAdapter見て、いつかRecyclerViewのAPIと並べて眺めると面白いのでは、と思ったのでメモ

*11:こういう値の時はViewを変更せず無視する、とか、一緒のViewBindingAdapterで管理してるバリデーションエラーを表示するViewを可視にする、とかそういう感じ?

*12:なんとなく、View本体とコールバックで使う変化した値を受け取るあたりの形がiOSのUIKitの各クラスに対応して定義されているDelegate書くときの形に似てる

*13:更新時のコールバックが呼ばれる前の値?

*14:多分取得はViewModelに渡すときのViewの属性値のgetterのラッパー。ここは単なる取得だけで、取得した値の加工はViewModelでやる。たとえば、SpinnerでRadioボタンを作って選択するとユーザーの入力は0, 1, 2 ...な整数値だけど、これをドメイン層のModelに渡す前にViewModelでDomain層に使うenum値に直すとか? こうするとCustomViewはドメイン層のModelのことを知らなくていいし、ModelもCustomViewの実装を知らなくて良い。取得メソッドは単なるラッパーであり、ラッパーから値を受け取るようにしているだけだが、この一皮のおかげでViewModelも直接Viewの参照を持たなくてよいため、テストが書きやすい? という気がした