データバインドの実装(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はプロパティ(アクセサの暗黙実装)を備えておらず、そのために規約としてアクセスの明示的な実装を必要とする。このせいでプロパティを類推する処理が必要になる。面倒だし無駄なことこの上ない。