21.5 C
Bangalore
September 23, 2018
Untitled
Technology

Introduction to RxJava: User Input Validation

In Part 1 we saw how RxJava can process long running operations easily without the hassle of implementing Async Tasks. In this lesson we will see how can we leverage RxJava operators to validate users input.

Input Validation

Validating keyboard inputs entered by user is a common use case in any application development. For example in a login page you have to validate a minimum criteria for username and password in client side before the input is submitted to server side. This often becomes complex when the screen has multiple input fields like firstname, lastname, dob, email, phone etc. Validating all of these inputs, maintaining the states of individual fields and combine the states of these fields to enable/disable a button is a tedious task in itself. Here comes operator called combineLatest for our rescue. CombineLatest combines the most recently emitted items from each of the source Observables, using a function we provide, and emits the return value from that function. So all our code for validating inputs can be put in one single place. Lets jump into example.

Example

Let’s take a login screen for our lesson. This login screen contains three input fields userName, Password and Email. There is a login button which is enabled only when all of the inputs satisfies a certain criteria.

Steps required:

  1. Create a Login Fragment with three input fields and a button
  2. Create three Observables, each one for userName, password and email.
  3. Using combineLatest operator combine above three observables.
  4. Subscribe to receive latest values from observables and enable/disable button.
  5. Create Patterns to validate inputs and apply.
  6. Unsubscribe from observables when no longer required.

Source code : https://github.com/jacksvarghese86/RxJavaIntro

Create Observables

combineLatest operator works on Observable objects. But what we have is three editText fields. So we need to map these editTexts to Observable objects in order to use with combineLatest operator.In above code, each editText is attached to an Observable(PublishSubject) so that it emits value when text in editText has changed. Here i have used PublishSubject, which emits items that it receives after subscription.

 public static Observable<String> getEditTextObservable(TextInputLayout inputLayout) { final PublishSubject<String> subject = PublishSubject.create(); inputLayout.getEditText().addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void afterTextChanged(Editable editable) { subject.onNext(editable.toString()); } }); return subject; } 

Move this piece of code to some utility class. Create all three observables.

 mUserNameObs = RxUtils.getEditTextObservable(mUserNameLayout); mPasswordObs = RxUtils.getEditTextObservable(mPasswordLayout); mEmailObs = RxUtils.getEditTextObservable(mEmailLayout); 

Combine observables

 Observable.combineLatest(mUserNameObs, mPasswordObs, mEmailObs, new Function3&lt;String, String, String, Boolean&gt;() { @Override public Boolean apply(String userName, String password, String email) throws Exception { return true; } }) 

We are not doing any validations for the time being. Function3 receives most recent values from the provided three observables when there is change in any of the observables. Our validation logic will go here. For the time being just return true.

Subscribe using an Observer

combineLatest will not emit latest values until it is subscribed. The value returned by Function3 is captured in Observer and button is enabled/disabled.

 Observable.combineLatest(mUserNameObs, mPasswordObs, mEmailObs, new Function3&lt;String, String, String, Boolean&gt;() { @Override public Boolean apply(String userName, String password, String email) throws Exception { return true; } }) .subscribe(new Observer&lt;Boolean&gt;() { @Override public void onSubscribe(Disposable d) { mCompositeDisposable.add(d); } @Override public void onNext(Boolean aBoolean) { mSignInButton.setEnabled(aBoolean); } @Override public void onError(Throwable e) { } @Override public void onComplete() { } }); 

Run the code and start typing username. We are expecting the button to be enabled now. But it does not happen. Reason is combineLatest operator starts emitting items only if all the observables have started emitting items. Now keep typing password and email. Login button should be enabled now. Awesome.

Apply validation

Create regex patterns to match against and put that in a utility class.

 public static final Pattern VALID_USERNAME = Pattern.compile("([a-zA-Z]{3,15})$"); public static final Pattern VALID_PASSWORD = Pattern.compile("([a-zA-Z0-9@*#]{8,15})$"); public static final Pattern VALID_EMAIL_ADDRESS = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); public static boolean validateEmail(String emailStr) { return VALID_EMAIL_ADDRESS.matcher(emailStr).matches(); } public static boolean validateUserName(String emailStr) { return VALID_USERNAME.matcher(emailStr).matches(); } public static boolean validatePassword(String emailStr) { return VALID_PASSWORD.matcher(emailStr).matches(); } 

Modify our implementation of Function3 to apply validation logic.

 new Function3&lt;String, String, String, Boolean&gt;() { @Override public Boolean apply(String userName, String password, String email) throws Exception { int failCount = 0; if (!InputValidator.validateUserName(userName)) { ++failCount; mUserNameLayout.setError("Username format not correct"); } else { mUserNameLayout.setErrorEnabled(false); } if (!InputValidator.validatePassword(password)) { ++failCount; mPasswordLayout.setError("Password format not correct"); } else { mPasswordLayout.setErrorEnabled(false); } if (!InputValidator.validateEmail(email)) { ++failCount; mEmailLayout.setError("Email format not correct"); } else { mEmailLayout.setErrorEnabled(false); } return failCount==0; } }) 

A false is returned if any of the input field fails to validate. Based on this return value login button is enabled/disbaled.

Unsubscribe

Subscription is disposed when fragment is destroyed. Thats all required to build a simple form validation screen using RxJava.

What Next

In the example, we had to manually take care of subscription and unsubscription, and forgetting to unSubscribe will lead to memory leak. We can combine RxJava and Databinding together and view will take care of subscribe and unsubscribe. We can see more about that in next tutorial.

Related posts