Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

 例えばショッピングサイトとかの発送先指定のフォーム『登録されている住所とは違う住所に送りたい時、「別の住所に送る」をチェックする、すると「住所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 なら、相当スッキリするんだけどなあー

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした