Since I started learning about RxJava, I have always been fascinated about how it can be used to make my life easier, code more fun and readable. I have successfully implemented Volley with RxJava to implement REST API calls, most of the async loading (images, files, etc). My worst enemy is forms. Forms need real time validations and real time responses to make UX better. In this post, I talk about my obsession about
EditText. Here I am trying to solve it so that I can get some peace of mind.
- Make the user fill
- Validate username, email and phone number with Regex
- Check with the server if the username and email are available or not
- If everything is validated, enable
How to go about it?
- RxJava Debounce - I talk about how debounce can be used for this purpose
- Chain Observables to make it RxJava-ish (Which helps in scheduling)
combineLatestoperator to combine all the observables to get final result - Enable/Disable the submit button
Okay, so we have our goals and little how-tos. Now, it’s time to write some code.
Observableto EditText so that it emits data when text has changed.
Here, I have used
PublishSubjectto emit EditText’s field value whenever it is being changed. The difference between
PublishSubjectis that, the former when subscribed to emits one previous item (if any), where as the latter emits only the items that it receives after the subscription. In this case, we don’t require
We have an Observable which will emit data as the user changes the value. Before going into validations, I just want to check how I can easily combine all my observables so I don’t have to keep checking values each time to enable/disable the button.
combineLatestto combine my observables.
I ran my code and I noticed something weird. I had just entered username (didn’t do anything to email and phone field). But I didn’t get anything.
combineLatestdidn’t emit anything. I started adding some text into email field and the result was the same. As soon as I entered first number in the phone field, my combineLatest function got called. Cleared phone number field and edited username field and yeah, I was getting data. I restarted the app and did the same thing again and got the same results. I was a bit confused and then I realized how
combineLatest operator starts emitting items only if all the observables have started emitting items. Since I had not changed text of email or phone after attaching an observable, the observable hadn’t started emitting items. I got items when all the observable had emitted at least one item. Pretty neat.
We have hooked observables to edittexts and getting one boolean using combineLatest. It’s time to add some pattern validations.
debounceto get the latest change in the editText and put it through our validator and combine results of validators to enable/disable submit button.
We have a debounce operator which emits item which are validated using
mapoperator which then sends item to our subscriber. Read up about my confusing about debounce here. After debounce, we have used
observeOn(AndroidScheduers.mainThread())because by default debounce operator works on scheduler thread and we need the data on the main thead as we’ve to show the results in the UI.
ValidationResult? It’s just a simple class that I made to keep some data about the validation. It stores whether the validation was successful or not. If not, it also keeps a reason which we can show to the user. Apart from that, it also stores the actual data which we’ll require later. You can find the class here.
Our validation is done and now we need to combine all the validation observables and emit a single result.
This looks like step 1 but with validations included. Great. Now, we need to move on to server based validation where we need to check if username or email is available or not.
mapoperator which will make API call to the server.
Wow, that’s a long chain of command. Well, nothing that lambdas can’t fix.
Moving on from step 3, we chain a
mapoperator which will call the API and return
ValidationResult. We need to process this result and make changes in the UI if validation fails so we attach another
mapoperator which will check the result and reflect it on the UI and emit validation success result. Notice, that we have moved the
observeOncall to right before the map where we change the UI. Here, for API call I could have used a
flatMapbut hey, if
mapworks, why use
In the code, you’ll notice that if Regex validation is not successful, the app is not making a call to the network. That’s some sneaky optimization right there. Am I right?
AvailabilityChecker is an interface and RandomAvailabilityChecker is an implementation which uses
delayoperator to send results after some amount of time. It also uses a random number to determing whether to send success or not.
Build. Run the app. Test. Yeah, it works but something is not right. I start entering the username and enter 5-6 characters. Regex validation is fine. The app is making a network call and I start editing the field again and add 2 more characters. Regex validation is successful and the app makes another network call. I get the response from my first network call. Wait, I don’t want that. I should have canceled that call before making another network call.
How do I do that? How do I cancel that network call before making a new call? So I looked around on the internet to find possible soultions and I didn’t find something satisfying. The only thing that I found was that I need to call
unsubscribeon the subscription to cancel the call. Well, I don’t have a subscription here and even if I had, I am pretty sure it would unsubscribe everything including the textwatcher subject.
Create a subscription for the API call
A bit messy and less RxJava-ish I suppose. We now have one PublishSubject for EditText and another one for validation which is directly hooked into
combineLatest. In the code, you’ll probably notice that I have again changed
observeOn()and moved it before the we touch the UI.
subscribeOn()call is probably redundant here as
debouncewill emit item on scheduler thread itself. As soon as we get the data from debounce, we cancel the api call and make a new call.
Combining subjects for final UI change
Subject is Observer as well as Observable so we can use them together in combineLatest.
Build. Run the app. Test. Yeah, works well. Pats on the back. But hey, wait a minute. There is thing weird thing happening. I entered 6-7 characters in the username and waited for a second. Network call started and I also started entering the text. I didn’t stop entering for 5 seconds and the network call returned data and it reflected on the UI. But I was entering the text so ideally that network call should have been cancelled as there was nothing to validate.
Another Aha! moment about
debounce. If I keep entering text, the subject will keep emitting an item and debounce will not send me any data until time-interval has gone by without any new item. Super sneaky debounce, I must say. Or am I too clumsy? Anyway,
mapto the rescue.
debounceto cancel API calls
Build. Run. Test. Works perfectly. Nice. Finally, I can get some peace in my life.
So after using bunch of
map operators here and there with cool dudes like
combineLatest, I finally managed to write a form validator using RxJava. I’m sure I could have eliminated a lot of
map operators, but where’s the fun in that! If you know how I can do this same thing and make it more RxJava-ish, please let me know.
I have uploaded the code on Github and you can find it here - RxFormValidation. I’m planning to add some unit testing and more form elements later.
P.S. Can you guess which is my favorite RxJava operator? It has
map in it.