例えばショッピングサイトとかの発送先指定のフォーム『登録されている住所とは違う住所に送りたい時、「別の住所に送る」をチェックする、すると「住所2」が必須入力となり、入力するまで次へ進めない』的なちょっと込み入ったValidationをReactive ExtensionsのJava版、RxJavaとRxAndroidでやってみました。
まずいきなり動作結果から。
こんな仕様です。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
// 「注文を確定する」ボタン
final Button buttonSubmit = (Button)findViewById(R.id.buttonSubmit);
// チェックボックスのON/OFFをObservable化
final Observable<Boolean> useSecondaryAddress =
ViewObservable.input((CheckBox) findViewById(R.id.checkUseSecondary), true)
.map(new Func1<OnCheckedChangeEvent, Boolean>() {
@Override
public Boolean call(OnCheckedChangeEvent onCheckedChangeEvent) {
return onCheckedChangeEvent.value;
}
});
// 住所1をObservable化
final Observable<OnTextChangeEvent> primaryAddress =
ViewObservable.text((EditText) findViewById(R.id.editPrimaryAddress), true);
// 住所2をObservable化
final Observable<OnTextChangeEvent> secondaryAddress =
ViewObservable.text((EditText) findViewById(R.id.editSecondaryAddress), true);
// チェックボックスと住所2の必須条件をObservable化
final Observable<Boolean> secondaryIsValid =
Observable.combineLatest(useSecondaryAddress, secondaryAddress,
new Func2<Boolean, OnTextChangeEvent, Boolean>() {
@Override
public Boolean call(Boolean useSecondary, OnTextChangeEvent secondaryAddress) {
if (!useSecondary) {
return true;
}
return !TextUtils.isEmpty(secondaryAddress.text);
}
});
// 全部まとめると、
// 住所1は入力必須、
// 住所2はチェックボックスがONの時だけ入力必須
// 必須条件を満たしていたらtrueを流す
final Observable<Boolean> isValidAll = Observable.combineLatest(primaryAddress, secondaryIsValid,
new Func2<OnTextChangeEvent, Boolean, Boolean>() {
@Override
public Boolean call(OnTextChangeEvent primaryAddress, Boolean isValidSecondary) {
if (!isValidSecondary) {
return false;
}
return !TextUtils.isEmpty(primaryAddress.text);
}
});
// 購読、監視
isValidAll.subscribe(new Observer<Boolean>() {
@Override
public void onNext(final Boolean isValid) {
// 必須条件を満たしていたら「注文を確定する」を有効にする
runOnUiThread(new Runnable() {
@Override
public void run() {
buttonSubmit.setEnabled(isValid);
}
});
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
});
}
ViewObservable.xxx
で、UI要素をObservable化します。これはRxAndroidの機能。これでテキストの変更とか、チェックボックスの変更のたびに、OnNext
が発生するようになります。
Validationでは、RxJavaの機能である Observable.combineLatest
がキモで、こいつに2つのObservableを渡してやると、その片方が値が変化した時に、T3 call(T1 a, T2 b)
が呼ばれます。T1、T2 は渡すObservableの型、T3は後続へ流す型で、ValidationなのでBoolean
です。
上記 secondaryIsValid
の実装では、「住所2に配送する」のチェックボックスと「住所2」のテキストボックスの2つのObservableを渡していて、
true
を返すtrue
を返すとしています。
次に、isValidAll
の実装では、「住所1」と secondaryIsValid
を渡していて、
secondaryIsValid
が false
なら false
を返すsecondaryIsValid
が true
で、且つ「住所1」が空でなければ true
を返すという実装です。
んで、こいつ(isValidAll
)を購読(subscribe
)すると、onNext
にValidationの結果が通知されるので、ボタンのEnabled
を切り替えます。
conbimeLatest は、本家Rxなら obsA.CombineLatest(obsB, (tA, tB) => tX).CombineLatest(obsC, (tX, tC) => tY)... とチェインして書けるのですが、RxJava の combineLatest はなぜか static メソッドしかなくてチェインできません、残念。
このレベルだと、すべてのUI要素に変更通知を仕込んで共通な関数を呼ぶ、的な実装で問題ないですが、要素や条件が増えてくると大変です。
Observable と combineLatest を使うと、制約の一部を(Observableに)部分化できて、それらを組み合わせるのも自由自在(Observableだから)。
Javaなのでかなり長ったらしくて読みづらいコードになってしまいました。
Xamarin.Android + 本家Reactive Extensions + ReactiveProporty なら、相当スッキリするんだけどなあー