データバインドの実装(1)

この日記では過去に何度もデータバインドの実装について書いてきた。

Androidに関してはそもそもモバイル機器であり、できるだけCPUサイクルやメモリを使いたくないことを考えると、WindowsForms(.NET Framework)やJava BeansBindingのようなモデルとビューのバインド(結合)を即時に実行することは避けたほうが良い。そのためには、ASP.NET MVCのモデルバインダのような、要求時にバインドする方法が良いだろうと過去に結論づけた。

[Android]データバインド
[Android]第3の選択

もう半年も前だった訳だが、現在は以下のようなインタフェースでデータバインドの実装を行っている。(本当は全てのコードを紹介したいのだが、その性格上、非常に多岐に渡るため全てを載せることは難しい。機会があればまとめてどこかにアップしたい)

public IDataBinding {
     T parseAs(Class asType, String... includes);
     T parseAs(Class asType, IDataBindListener callBack, String... includes);
     void assignFrom(T model, String... includes);
     void assignFrom(T model, IDataBindListener callBack, String... includes);
}

このメソッドを実装するのはContextの実装クラス、つまりはActivityやServiceの派生クラス又はその委譲クラスであることを前提にしている。(もちろんActivityクラスに直接処理を書く訳ではないが)

parseAsメソッドはそのコンテキストから指定した型(asType)のオブジェクトを生成してデータバインドを実行するメソッドであり、assginFromメソッドはその逆に、コンテキスト中の任意のフィールド又はプロパティにモデルの値を同期する。パラメタ includesはバインドの組合わせ(データとフィールド又はプロパティ)を識別する名前をセットすることで、バインドする対象を限定できる。


バインドの対象はコード又は@BindFieldアノテーションで指定する。

    @InjectView(R.id.EditText01) @BindField(property={"text"})
    public EditText name;
    
    @InjectView(R.id.EditText02) @BindField
    public EditText address;
    
    @InjectView(R.id.EditText03) @BindField
    public EditText tel;
    
    @InjectView(R.id.EditText04)
    public EditText dog;
    
    @InjectView(R.id.Spinner01) @BindField(property={"SelectedItemPosition","Selection"})
    public Spinner gender;
    
    @InjectView(R.id.CheckBox01) @BindField(property={"checked"})
    public CheckBox married; 

ビュー側(Activity)のデータバインドはフィールドとして定義したビュー(View)に対して@BindFieldアノテーションを記述することで指定する。
property属性は実際にバインドするビューのプロパティを設定するが、一つだけ指定された場合はその名前を元にしたgetter/setter、二つ指定した場合はそれぞれが読み込み/書き込みのアクセサ、そして何も設定されない場合"Text"がデフォルトとして指定される。(実際にはgetText/setTextのペアだが) ※
モデルとビューをバインドするためにはそれらを結びつける"何か"が必要だが、現在の実装ではアノテーションで明示的に指定するか、指定しない場合はフィールド名かプロパティ名を使って結合される。

  • 例: EditText型のフィールドnameのtextプロパティをクラスHogeのフィールド"name"にバインドする
    @BindField(property={"text"})
    public EditText name;←┐
    :                      │
    :                      │
                           │TextプロパティとHoget.nameを結合する
    public class Hoge {    │
                           │
        String name; ←──┘
    }
  

ただし、結合されるといっても現段階では上記のparseAs又はassignFromメソッドを実行した時に値の同期を行うが、それ以外には同期を実行しない。

ちなみに@InjectViewは外部からリソースを注入するアノテーションでありデータバインドとは関係無い。ただ、本方式でデータバインドを行う場合、Activityが保持しているビューはフィールドかプロパティとして参照できる必要があるため、実質このアノテーションも必要となる。

AndroidアプリケーションにおいてMVCの扱い、特にActivityの役割をどう考えるかは必ず議論になるが、私はActivityはできるだけビューと考えてロジックは書かず、モデル側もビューとの依存性はもたないように(単独でテストできるように)設計、実装する。

Javaはプロパティ(アクセサの暗黙実装)を備えておらず、そのために規約としてアクセスの明示的な実装を必要とする。このせいでプロパティを類推する処理が必要になる。面倒だし無駄なことこの上ない。