なか日記

一度きりの人生、楽しく生きよう。

Android Bindingを改めて使ってみた(1)

本田さんのエントリ「android-binding使ってみた « Code Archives」でトラックバックをもらったこともあって改めてAndroid Bindingについて考えてみました。

ベースは半年前のエントリです。
Android Bindingを使ってみた(1) - タイトルは未定
Android Bindingを使ってみた(2) - タイトルは未定
Android Bindingを使ってみた(3) - タイトルは未定

このエントリでの最終的なコミットはこちら
https://github.com/nakaji/AndroidBindingSample/commit/1640c95546fff70218eeac050a8b7c4bb4f85ff1

当初の目的と前回までの課題

自分でもすっかり忘れてたので復習。

目的
  • ユニットテストを行いやすくしたい
    • 画面(View)とロジック(ViewModel、Model)を分離する
    • ViewModelからAndroidに依存するライブラリを分離する

Activityにロジックを書いてしまうと、画面を実際に操作してテストするしか方法がなくなってしまいます。これは面倒くさいですね。できればロジックはJUnitなどのツールで気軽に何度も確認できるようにしたいものです。そのために、ロジックと画面表示に関係するコードは分離しましょうというわけです。
しかし、AndroidJUnitエミュレータ上で動くので、とにかく遅い。それがイヤなので、エミュレータを使用しないでJUnitでテストができるように、ViewModelからAndroidに依存するライブラリを分離したいわけです。

別のいい方をすると、Activity等のロジックに関わるところで画面の各ViewをfindViewByIdで取得しますが、これをやってしまうとロジックが画面のことを知っておかないといけません。この依存をなくしたいわけです。
簡単に言うと、「findViewByIdするのめんどくせー」ってことかなw

結果

PojoViewModelを使うことで分離はできたけど、課題あり。

最終的に残った課題
  • View→ViewModelへのバインドができない
    • PojoViewModelクラスを使用
      • ViewModel→Viewの片方向しか対応してない

そのため、画面で変更された値をViewModelに反映するためには結局、ViewModel側でViewのことを知っておかないといけない*1ということ。
なので、前回は「可能性を感じつつ、今後に期待」って感じでした。

改めて眺めてみて

以下のような理由から、PojoViewModel使わない方がいいような気がしてきました。

  • View→ViewModelへのバインドができない
  • Observable使った方が何かと幸せな気がする
    • サポートされてるViewの種類が多い*2
    • 双方向バインドのサポート


というわけで、PojoViewModelを使用せず、CommandとObservableを使って実装してみました。

BMIViewModelクラスの変更

値についてはデータの変更を通知してくれるObservable関連のクラスを使うようにします。
ボタンを押したときの処理は、Commandを使用します。Invokeメソッドをオーバーライドしますが、Android固有のViewクラスを参照することになりますが、これは使っちゃダメ。ゼッタイ

package com.nakaji.android.bmicalc;

import gueei.binding.Command;
import gueei.binding.observables.DoubleObservable;
import gueei.binding.observables.StringObservable;
import android.view.View;

public class BMIViewModel {

    public DoubleObservable BMI = new DoubleObservable();
    public StringObservable height = new StringObservable();
    public StringObservable weight = new StringObservable();

    public Command Calculate = new Command() {

        @Override
        public void Invoke(View arg0, Object... arg1) {
            Calculate();
        }
    };

    private void Calculate() {
        double bmi = Double.parseDouble(weight.get()) / Math.pow(Double.parseDouble(height.get()) / 100.0, 2);
        BMI.set(bmi);
    };
}

BMIActivityクラスの変更

ViewModelのインスタンスをバインドするだけになりました。findViewByIdしなくて良い分、だいぶすっきりしました。

package com.nakaji.android.bmicalc;

import com.nakaji.android.bindingsample.R;

import gueei.binding.Binder;
import android.app.Activity;
import android.os.Bundle;

public class BMIActivity extends Activity {

    private BMIViewModel viewModel = new BMIViewModel();

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Binder.init(this.getApplication());
        Binder.setAndBindContentView(this, R.layout.main, viewModel);
    }
}

main.xml(レイアウト)

画面を構成する要素(EditText、TextView、Button)全てについてバインドすることができます。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:binding="http://www.gueei.com/android-binding/"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	>
	<TextView
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="身長"
		/>
	<EditText
		android:id="@+id/height"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:inputType="numberDecimal"
		binding:text="height"
		/>

	<TextView
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="体重"
		/>
	<EditText
		android:id="@+id/weight"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:inputType="numberDecimal"
		binding:text="weight"
		/>

	<Button
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="計算"
		binding:onClick="Calculate"
		/>

	<TextView
		android:id="@+id/buttonCalculateBMI"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		binding:text="BMI"
		/>
</LinearLayout>

中間的なまとめ

上記でAndroid Bindingを使用した一つの実装ができました。
でも、元々の目的だった「ViewModelからAndroidに依存するライブラリを分離する」というのができてません。
分離したかった理由は、AndroidのUnitTestがエミュレータ上で動くので遅い→リズムが悪い→テストを実行するのが面倒くさくなるという状態を避けたかったためです。
個人的には、極端な話「分離できてなくてもリズム良くテストが実行できるならそれでもいいんじゃないの?」と思うので、そっちの方向で進めようと思います。
次のエントリ(その2)では、できあがったものをどうやってUnitTestするか考えることにします。

*1:findViewByIdでViewを取得し、テキストが変更された際にViewModelへ反映というコードを書く必要があった

*2:http://code.google.com/p/android-binding/wiki/BindableAttributes