RxProperty で最初にボタンを押すまでバリデーションしない方法

こちらの件です。

私見

自分がよく関わっている業務アプリの世界では、UIパーツの非活性(disabled)はあまり好まれません(度々 Reject されます)。その一番の理由は、「なぜボタンが押せないのかが分からない」ことです(利用ユーザーがIT機器に疎い人が多いのでそれだけでパニクることも)。ならばその理由を画面上のどこかに表示してやろうと策を練るよりも、単純に「エラーがあったら DialogBox でその理由と対処方法を表示させたろう」という方法が、実装コストが最も低く、業務アプリ利用者にもわかりやすい、というのが経験・感覚的にあります。

しかしそれではあまりにもモダンでないとも感じるので、個人的には、次点として採用したいのは↓の手法です。

実際にやってみた

実際にそれを実装してみました、 RxProperty を使って。 最近ちょうどフォームバリデーションのサンプルを実装した例↓

があるので、これをカスタマイズしてみます。

できあがり

こんな感じのものを作ります。

Untitled.gif

修正箇所

RxProperty は、ViewModel が公開するプロパティとして利用するものですが、それに Validator を持たせることができます。

val nickname = RxProperty<String>("")
    .setValidator {
        if (it.length < 2) "ニックネームは2文字以上にしてください" else null }

のようにプロパティの定義と共に設定すれば、画面表示直後からValidatorは作動しますが、ボタンが押されるまではバリデーションしないのであれば、設定のタイミングを遅らせるだけです。

val nickname = RxProperty<String>("")
val nickNameValidator : (String)->String? = {
    if (it.length < 2) "ニックネームは2文字以上にしてください" else null }

private var isFirstExecute = true

/** 登録ボタンを押したとき */
val register = canRegistration.toRxCommand<NoParameter>().apply { this.subscribe {
    // 最初にボタンが押されたときに、Validator を設定する(フラグを使っているのがなんかダサい)
    if (isFirstExecute) {
        isFirstExecute = false

        nickname.setValidator(nickNameValidator, true)

        if (!this.canExecute()) { return@subscribe }
    }

    _toast.postValue("RegistrationCompleteActivity へ移動するよ")
}

ちなみに

val nickname = RxProperty<String>("")
    .setValidator ({
        if (it.length < 2) "ニックネームは2文字以上にしてください" else null },
        false)

とする(setValidator の第2引数を false にする)と、画面表示直後の初回のバリデーションを行わない、すなわち、「最初から登録ボタンは押せるが、リアルタイムにバリデーションも行う」ようにもできます。

RxProperty に限らないんですけど、プロパティ/バリデータ/活性非活性判定・変更処理が適切に分離されていれば、それらの組み合わせを変えるだけなのでいかようにでもできますね。急な仕様変更にも割と容易に対応できるということで。