RxJava2表单验证

时间:2017-11-10 09:23:49

标签: android validation rx-java2

我有一个表格,其中有4个可能的选项需要检查(根据具体情况可能会更少)。创建订单时有2个editexts,一个用于电子邮件,另一个用于参考字段。

根据条件(创建表单时可用),电子邮件和参考字段可能会保留为空,也可能不会为空。此外,我们可能需要显示一个警告对话框,告诉用户可能无法显示参考值(对订单的收件人),他们可能还需要同意条款和条件警报对话框。

目前onConfirm检查是这样的,

void onCreateOrderConfirmed(@Nullable final String receiverEmail,
                            @Nullable final String reference,
                            @Nullable final Boolean noRefAgreed,
                            @Nullable final Boolean termsAndConditionsAgreed) {

    if (!reviewCompletionState.emailRequirementSatisfied()) {
        if (!isValidEmail(receiverEmail)) {
            view.showEmailError();
            return;
        }

        reviewCompletionState = reviewCompletionState.newBuilder()
                .receiverEmail(receiverEmail)
                .emailRequirementSatisfied(true)
                .build();
    }

    if (!reviewCompletionState.referenceRequirementSatisfied()) {
        if (isEmpty(reference)) {
            view.showReferenceError();
            return;
        }

        reviewCompletionState = reviewCompletionState.newBuilder()
                .reference(reference)
                .referenceRequirementSatisfied(true)
                .build();
    }

    if (!reviewCompletionState.noRefAgreed()) {
        if (noRefAgreed == null || !noRefAgreed) {
            view.showNoReferenceAlert();
            return;
        }

        reviewCompletionState = reviewCompletionState.newBuilder()
                .noRefAgreed(true)
                .build();
    }

    if (!reviewCompletionState.termsAndConditionsAgreed()) {
        if (termsAndConditionsAgreed == null || !termsAndConditionsAgreed) {
            view.showTermsDisclaimerAlert();
            return;
        }

        reviewCompletionState = reviewCompletionState.newBuilder()
                .termsAndConditionsAgreed(true)
                .build();
    }

    createOrder();
}

我很想知道是否有办法使用RxJava2简化此验证? (但目前还不知道能够做到这一点)

TIA

2 个答案:

答案 0 :(得分:5)

这可能很简单。会有很多代码,我会先显示结果。

 ruby -run -ehttpd . -p9001

首先,为reviewState创建模型:

    private ReviewValidator reviewValidator = new ReviewValidator();

    void onCreateOrderConfirmed(@Nullable final String receiverEmail,
                                @Nullable final String reference,
                                @Nullable final Boolean noRefAgreed,
                                @Nullable final Boolean termsAndConditionsAgreed) {
        ReviewState reviewState = new ReviewState(receiverEmail,
                reference,
                noRefAgreed,
                termsAndConditionsAgreed);//another model for simplicity

        reviewValidator.validate(reviewState)
                .flatMap(reviewState -> /* create order */)
                .subscribe(this::onOrderCreated, this::onOrderCreatingError);

    }

    void onOrderCreated(Object order) {//or what you need here
        //handle positive result
    }

    void onOrderCreatingError(Throwable throwable) {
        if (throwable instanceof ValidateException) {
            List<ValidateError> errors = ((ValidateException) throwable).getValidateErrors();
            for (ValidateError error: errors) {
                switch (error.getField()) {
                    case EMAIL: {
                        view.showEmailError();
                        return;//or break if you want show all errors
                    }
                    case REFERENCE: {
                        view.showReferenceError();
                        return;
                    }
                    //handle another errors....
                }
            }
        //handle another error cases...
    }

然后创建自己的验证器。没有必要创建一个完整的模型,你可以为每个字段创建验证器,并将它们与你选择的flatMap()链接起来。

public class ReviewState {

    private String receiverEmail;
    private String reference;
    private Boolean noRefAgreed;
    private Boolean termsAndConditionsAgree;

    public ReviewState(String receiverEmail,
                       String reference,
                       Boolean noRefAgreed,
                       Boolean termsAndConditionsAgree) {
        this.receiverEmail = receiverEmail;
        this.reference = reference;
        this.noRefAgreed = noRefAgreed;
        this.termsAndConditionsAgree = termsAndConditionsAgree;
    }

    public String getReceiverEmail() {
        return receiverEmail;
    }

    public String getReference() {
        return reference;
    }

    public Boolean getNoRefAgreed() {
        return noRefAgreed;
    }

    public Boolean getTermsAndConditionsAgree() {
        return termsAndConditionsAgree;
    }
}

验证器的抽象类:

public class ReviewValidator extends Validator<ReviewState> {

    @Override
    protected List<ValidateFunction> getValidateFunctions(ReviewState reviewState) {
        List<ValidateFunction> validateFunctions = new LinkedList<>();
        validateFunctions.add(() -> validateEmail(reviewState.getReceiverEmail()));
        validateFunctions.add(() -> validateReference(reviewState.getReference()));
        //another validation methods
        return validateFunctions;
    }

    private ValidateError validateEmail(String email) {
        if (TextUtils.isEmpty(email)) {
            return new ValidateError(Field.EMAIL);//Field.EMAIL - just enum
        }
        return null;
    }


    private ValidateError validateReference(String reference) {
        if (TextUtils.isEmpty(reference)) {
            return new ValidateError(Field.REFERENCE);
        }
        return null;
    }
    //....
    //another validation methods
}

验证器的助手类......

public abstract class Validator<Model> {

    public Single<Model> validate(Model model) {
        return Single.just(model)
                .map(this::validateModel)
                .flatMap(this::processResult);
    }

    private Single<Model> processResult(ValidateResultModel<Model> validateResultModel) {
        return Single.create(subscriber -> {
            List<ValidateError> validateErrors = validateResultModel.getValidateErrors();
            if (validateErrors.isEmpty()) {
                subscriber.onSuccess(validateResultModel.getModel());
            } else {
                subscriber.onError(new ValidateException(validateErrors));
            }
        });
    }

    private ValidateResultModel<Model> validateModel(Model model) {
        List<ValidateError> errors = new LinkedList<>();
        for (ValidateFunction validateFunctions : getValidateFunctions(model)) {
            ValidateError error = validateFunctions.validate();
            if (error != null) {
                errors.add(error);
            }
        }
        return new ValidateResultModel<>(model, errors);
    }

    protected abstract List<ValidateFunction> getValidateFunctions(Model model);

    protected interface ValidateFunction {

        @Nullable
        ValidateError validate();
    }
}

最初,我从这里接受了这个想法:https://github.com/matzuk/TestableCodeMobius/tree/master/app/src/main/java/com/matsyuk/testablecodemobius/business/transfer/validation

答案 1 :(得分:3)

我认为你应该RxJava CombineLatest ,所以你需要所有的表单输入都产生一个observable,然后你只需要组合它并调整视图

作为参考,您可以查看:

https://medium.com/@etiennelawlor/rxjava-on-the-sign-in-screen-9ecb66b88572

Using RxJava for email login validation, an observable is emitting twice

======

示例:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding2.widget.RxCompoundButton;
import com.jakewharton.rxbinding2.widget.RxTextView;

import io.reactivex.Observable;


public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    EditText receiverText = findViewById(R.id.input_receiver);
    EditText referenceText = findViewById(R.id.input_reference);
    CheckBox checkRef = findViewById(R.id.check_ref);
    CheckBox checkTerms = findViewById(R.id.check_terms);
    Button buttonLogin = findViewById(R.id.button_login);

    Observable<CharSequence> receiverObservable = RxTextView.textChanges(receiverText).skip(1); // can add more logic
    Observable<CharSequence> referenceObservable = RxTextView.textChanges(referenceText).skip(1); // can add more logic
    Observable<Boolean> refCheckObservable = RxCompoundButton.checkedChanges(checkRef); // can add more logic
    Observable<Boolean> termsCheckObservable = RxCompoundButton.checkedChanges(checkTerms); // can add more logic

    Observable<String> combineObservable = Observable.combineLatest(
            receiverObservable,
            referenceObservable,
            refCheckObservable,
            termsCheckObservable, (receiverCharSequence, referenceCharSequence, refBoolean, termsBoolean) -> {
                // add logic here for now it is only combine the input
                return  receiverCharSequence + " " + referenceCharSequence + " " + refBoolean + " " + termsBoolean ;}
            );

    RxView.clicks(buttonLogin).flatMap(o -> { return combineObservable;}).distinctUntilChanged().subscribe(string -> {
        Toast.makeText(this, string, Toast.LENGTH_LONG).show();
    });

    }
}