今更遅れてDataBinding事始め(4)[双方向]

いままでのまとめ

やっと双方向bindingを色々と試せたので、自メモとして残しておく

動作環境

Android Studio 2.3 Beta 3

メモリ 16G

双方向binding

基本的な話

レイアウトの修正

@{vm.name}

=>

@={vm.name}

ObservableXXX であればそのまま双方向に動く(ObservableBoolean とか)

これは予想通りでたしかに問題ない。

用意されているObservableオブジェクト

  • android.databinding パッケージ
    • ObservableInt
    • ObservableBoolean
    • ObservableChar
    • ObservableFloat
    • ObservableDouble
    • ObservableLong
    • ObservableShort
    • ObservableByte

BaseObservableを継承しているプリミティブ型

  • android.databinding パッケージ
    • ObservableArrayList
    • ObservableArrayMap

配列型、Map型

  • android.databinding パッケージ
    • ObservableField
      • コレクション型を使いたい時用
      • 例) ObservableField firstName = new ObservableField<>();
    • ObservableParcelable

@Bindable の場合の挙動

extends BaseObservable が前提

  • layout/activity_main.xml
<layout>
  <data>
     <variable type="hoge.fuga.ViewModel" name="model"/>
  </data>   
  <EditText android:text="@={model.name}" />
  <Button android:enabled="@{model.btnEnabled}"/> //★★

のとき、下記はなぜか動かなかった

  • ViewModel(NG)
public class ViewModel extends BaseObservable {
    @Bindable
    private String name;

    public boolean getBtnEnabled() { 
        return !TextUtils.isEmpty(name); 
    }      

    public String getName() { return name; }

    public void setName(String name) {
       this.name = name;
       notifyPropertyChanged(BR.model); //◎
    }
}
  1. @Bindable アノテーションの記述位置
    1. 変数にBindableでもOK
    2. getter関数につけるのでもOK
  2. ◎の記述はなぜか動かない*1
notifyPropertyChanged(BR.model); //◎
mDataBinding.executePendingBindings();//◎◎

◎◎と記載してしまうと、常に画面が初期値にリセットされてしまう。。

下記のページの記載のように、model全体ではなく、

変数単位でbindすれば notifyPropertyChangedが動くのかも?*2

  • ViewModel(OK)
public class ViewModel extends BaseObservable {
    private String name;

    @Bindable
    public boolean getBtnEnabled() { 
        return !TextUtils.isEmpty(name); 
    }      

    @Bindable
    public String getName() { return name; }

    public void setName(String name) {
       this.name = name;
       notifyChange();//★
    }
}

★であれば、下記のページ記載と同じく連動する。

ただ //★★の記載でOKのようで、特に双方向しなくていい変数に関しては*3

@={vm.name} 

の記載は要らない。対象の変数を使っていれば、すべて連動するイメージ

其の場合、勿論 空のsetXXXは要らない


その他の事でなにげにハマること

String型以外を戻す時にautoキャストは動かない

public class ViewModel extends BaseObservable {
    private String name;

    @Bindable
    public String getName() { return name; }

    public void setName(String name) {
       this.name = name;
       notifyChange();//★
    }

    @Bindable
    public int getCount() {
       return name.length();
    }
}

ただ二個目のQiita引用だとさり気なくString返却されているから気づかないのですが、★★★がエラー

  • layout/activity_main.xml(NG)
<layout>
  <data>
     <variable type="hoge.fuga.ViewModel" name="model"/>
  </data>   
  <EditText android:text="@={model.name}" />
  <TextView android:text="@{model.count}"/> //★★★
  • layout/activity_main.xml(OK)
<layout>
  <data>
     <variable type="hoge.fuga.ViewModel" name="model"/>
  </data>   
  <EditText android:text="@={model.name}" />
  <TextView android:text="@{String.valueOf(model.count)}"/> //★★★

なキャストはしてやる必要があり。

もしくは下記な感じか。。

  <TextView android:text='@{"" + model.count}'/> //★★★

基本 “◎◎"抽出のはずなのですが、偶にパースできないみたいな記載が出る。

layout上で計算式書くとかはあまり推奨されないのかも。autoキャストも聞かないので厳密に書かないと駄目なようですし

原因的には

  <TextView android:text="@{model.count}"/> //★★★

だと下記と解釈されてしまうからのよう

  <TextView android:text="@{ @string/model.count[StringID] }"/> //★★★

こういうアプローチも有るけど

エラーになること自体はプリミティブ型というのが駄目なようなので

public class ViewModel extends BaseObservable {
    private String name;

    @Bindable
    public String getName() { return name; }

    public void setName(String name) {
       this.name = name;
       notifyChange();
    }

    @Bindable
    public Integer getCount() { //◎◎◎
       return name.length();
    }
}

//◎◎◎ とObject型を返却すれば TextView:textに設定してもエラーにはならない。

ただし、なぜか空白扱いで表示されない状態になってしまうため、痛し痒しかな・・*4

0が表示されるという記載があるんだけど、挙動が変化してるんだろうか? それとも環境依存?

参考

Viewクラスのimportをよく忘れる

  • △の箇所。
  • 暗黙import は 通常のjavaクラスのみみたいなんですよね〜(汗

    • しかもこの手の記述書くときは、補完効かないし。・・
  • layout/activity_main.xml

<layout>
  <data>
     <import type="android.view.View"/> //△
     <variable type="hoge.fuga.ViewModel" name="model"/>
  </data>   
  
  <EditText android:id="@+id/name" android:text="@={model.name}" />
  <TextView android:id="@+id/cnt"  android:text="@{String.valueOf(model.cnt)}"
            android:visibility="@{model.name == null || model.name == '' ? View.GONE:View.VISIBLE}"/>

ちょっとだけ楽になった所

layout.xml上の補完が多少効くようになった

ただし条件的には

  • layout.xmlがエラーが出ていない
    • layoutでエラーが出ている状態では、補完自体が全滅になる
  • GradleSyncを行っていること*5
  • layout.xml上ののパーツが多くないもの
    • previewを表示させながらだと、重すぎて補完候補自体が表示されないことがシバシバ*6

こうかんがえると、こまめにinclude文でlayout.xmlを分割する設計がただしいのかな? とか思ってしまったりもしますが・・

layout.xml上でid連動が出来るようになった

  • //△△ の記述 双方向bindingのでバックとかに便利

    • id.text とかで参照できるようになる(下記だと name.text の箇所)
  • layout/activity_main.xml

  <EditText android:id="@+id/name" android:text="@={model.name}" />
  <TextView android:id="@+id/cnt"  android:text="@{String.valueOf(model.cnt)}"
            android:visibility="@{model.name == null || model.name == '' ? View.GONE:View.VISIBLE}"/>

  <TextView android:text='@{"debug:" + name.text + '/' + model.cnt }'/> //△△

ただ通常のattibuteにはなくて、定義済みアトリビュートで対応しているものは、エラーになることが有る。

そのばあいは getXXX() と直接関数を書いてしまえばOK

  • 例)SeekBar
    • android:progress は本来は初期値のみ。これがdata-bindingでは拡張されている
<SeekBar android:progress=@={model.progress} />

<TextView android:text='@{"debug:" + model.progress }'/> //NG
<TextView android:text='@{"debug:" + model.getProgress() }'/> //OK

参考

data-bindingの属性がlayoutプレビューにも反映されるようになった

  • ただいまいち書ける&プレビューに反映される属性が不明。
  • 補完候補に出てこなくても、動くものも有るよう
<android.support.v7.widget.Toolbar
        app:title="ほげほげ" //△△△
        app:titleTextColor="@color/white" //△△△
        android:id="@+id/toolBar"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:minHeight="?attr/actionBarSize"
        android:background="?attr/colorPrimary">

*1:なにげにググると上記の記載例が有るページが有るんですが・・

*2:でもいちいちメンバ変数を羅列するのは億劫かも

*3:表示連動という意味ではない。値が双方向に変更させるものだけでよい

*4:ビルドエラーにならないだけ心臓に悪くない?

*5:補完候補が

*6:previewを消せば動くようになることも有る