47
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「チェックAがONならば、項目Bは入力必須とする」という Validation を RxJava + RxAndroid でやる

Last updated at Posted at 2014-11-10

 例えばショッピングサイトとかの発送先指定のフォーム『登録されている住所とは違う住所に送りたい時、「別の住所に送る」をチェックする、すると「住所2」が必須入力となり、入力するまで次へ進めない』的なちょっと込み入ったValidationをReactive ExtensionsのJava版、RxJavaRxAndroidでやってみました。

動作イメージ

 まずいきなり動作結果から。

  • 住所1は入力必須。
  • 住所2は「住所2へ配送する」がチェックされている場合のみ、入力必須。
  • 必須項目が入力されていない場合はボタンを押せない

こんな仕様です。

実装

@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を渡していて、

  • 「住所2に配送する」がOFFなら true を返す
  • 「住所2に配送する」がONで、且つ「住所2」が空でなければ true を返す

としています。

 次に、isValidAll の実装では、「住所1」と secondaryIsValid を渡していて、

  • secondaryIsValidfalse なら false を返す
  • secondaryIsValidtrue で、且つ「住所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 なら、相当スッキリするんだけどなあー

47
44
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?