RxAndroid Basics: Part 2
2016-02-10
Welcome Back! I'm excited that you've decided to learn a little bit more about RxJava and how it can be used with Android. If you missed Part 1, go check it out before reading ahead. Now let's jump into some more examples. Remember, the full set of examples is available in the form of a fully functional app. Also, at the beginning of each example, I'll link to the specific Activity in which the following code snippets live.
Example 4: Subjects
Let's make an Activity that displays a number, with a button to increment the number.
Before we jump into the code though, it's time to introduce another RxJava concept, the Subject
.
Subjects are special objects that are both an Observable
and an Observer
.
I like to think of Subjects as a pipe.
You can put things into one end of the Subject and it will come out the other.
There are several types of Subjects, but we're going to use the simplest one: a PublishSubject
.
With a PublishSubject
, as soon as you put something in one end of the pipe it immediately comes out the other.
We're going to hook up the "out" end of the pipe first.
We said that Subjects were Observables which means we can observe them like we would any other Observable.
This is how we "watch things come out of the other end of the pipe".
We set up a fairly simple Observer
that just changes what our mCounterDisplay
shows to the user.
mCounterEmitter ;
mCounterEmitter.;
Unlike our previous examples, we're actually going to have our onNext()
be called multiple times.
Every time a new value is emitted, we'll change the value of the mCounterDisplay
to that new value.
But how is our PublishSubject
going to emit values? Let's take a look at some code for the mIncrementButton
that we're using in conjuction with the mCounterDisplay
:
mIncrementButton.;
Ah ha! We see here the mIncrementButton
does two things in its onClick()
callback:
- It increments a variable called
mCounter
. - It calls
onNext()
on themCounterEmitter
with the new value ofmCounter
.
Since Subjects are also Observers that means they have an onNext()
method.
This means we can put stuff into the "in" end of the pipe by simply calling onNext()
.
The value will come out up in the onNext()
of the Observer we subscribed in the code above.
It's like on one end of the pipe we observe the increment button being clicked and we communicate that to the Observer
over on the other end of the pipe.
Example 5: Map()
Let's make an Activity that just displays a number. This is going to be a simple Acitivity, and we're just making it so that we can introduce a new concept: map. If you've ever worked with functional programming, you're probably familiar with the map operator. You can think of map as a function that takes in one value and outputs another value. Usually there is some relationship between value put in to the map and the value that is output.
Let's make a Single
that just emits the value 4.
Single...;
We want to eventually display the value our Single
emits, so we need to convert it from an Integer
to a String
.
One way we can do this is using map()
.
Like we said above, maps can take in one value and output another.
This suites our purpose quite well.
Since our Single will emit one Integer
of value 4, we'll use map()
to convert it to a String
, and then our Observer
will take care of actually displaying it.
This is a fairly trivial use of the map function. But maps are actually quite powerful! As we'll see in the next example, maps can be used to execute arbitrary code and help us transform data in very useful ways.
Example 6: Bringing it All Together
Let's make an Activity that can help a user search for cities by name. In this Activity, we're going to put everything we've learned together into one last mega-example. We're also going to add a new concept: debounce. Let's dive in.
We want to setup a PublishSubject
such that it receives values the user types into a search box, fetches a list of suggestions based on that query, and then displays them.
Let's set that up:
mTextWatchSubscription ;
mSearchInput.;
There's a lot here, so let's go through things one-by-one.
The first thing you'll notice is debounce()
.
What is this, and why do we need it?
Well if you look at how we've setup the TextWatcher
, you'll notice a new value is going to come into our PublishSubject
every single time the user adds or removes a character from their search.
This is neat, but we don't want to send out a request to the server on every single keystroke.
We'd like to wait a little bit for the user to stop typing (so that we're sure we've got a good query) and then send our search request to the server.
This is what debounce()
allows us to do.
It tells mSearchResultsSubject
to only emit the last value that came into it after nothing new has come into the mSearchResultsSubject
for 400 milliseconds.
Essentially, this means our subject won't emit the search string until the user hasn't changed the string for 400 milliseconds, and at the end of the 400 milliseconds it will only emit the latest search string the user entered.
Perfect! This should help us avoid unnecessary searches and a UI that is constantly changing as a result of every single keystroke.
We want to use what the debounce emits to query our server via our RestClient.
Since querying our RestClient is an IO operation we need to observe the emissions of debounce on the IO Scheduler.
So boom, observeOn(Schedulers.io())
.
Cool, so now we're emitting our search queries onto the IO Scheduler. This is where the magic of map comes in. We're going to use map to "map" our search queries to a list of search results. Because map can run any arbitrary function, we'll use our RestClient to transform our search query into the list of actual results we want to display.
Since our map was run on the IO Scheduler, and we want to use the results it emits to populate our views, we then need to switch back to the UI thread.
So we add an observeOn(AndroidSchedulers.mainThread())
.
Now we've got the search results being emitted on the UI thread.
Note the ordering of all our observerOn()s here.
They're critical.
We've essentially setup the following order of emissions:
mSearchResultsSubject
|
|
V
debounce
|||
|||
V
map
|
|
V
observer
The |
represents emissions happening on the UI Thread and the |||
represents emissions happening on the IO Scheduler.
Finally, we use the results from the search to display the search results to the user. Pretty neat, right?!
Wrapping Thing Up
Well that's it for me. I hope these examples have been helpful in teaching you some of the RxJava basics. There are of course many more aspects of RxJava that I strongly encourage you to explore. If you have any feedback for me, or any other examples you'd like to see, please drop me a line. And if you have any examples you'd like to add to our repository, please feel free to submit a pull request. Happy coding!
Further Reading
For “safer” Subjects, try RxRelays.
Some details regarding Schedulers, observeOn, and subscribeOn
debouce() is actually just one of many fun operators available in RxJava.