Schedulers

Although RxJava is heavily marketed as an asynchronous way of doing reactive programming, it’s important to clarify that RxJava is single threaded by default, and you need to specify otherwise, and that’s where Schedulers come in.

A quick reminder of the difference between Synchronous vs Asynchronous.

With Synchronous programming, only one thing happens at a time. The code fires up method a, which Reads/Write from the database, and waits for a to finish before moving on to b. So you get one thing happening at a time, and it’s the most common cause for UI freeze since the code will also run in the same thread as the UI.

With Asynchronous programming, you can call many methods at once, without waiting for another to finish. It’s one of the most fundamentals aspects of Android Development, you do not want to run every code on the same thread as the UI, especially computational code.

subscribeOn and observeOn

These methods allow you to control the action of the subscription and how you receive the changes.

subscribeOn

With subscribeOn you get to decide which thread your Emitter (such as Observable , Flowable , Single , etc) is executed.

The subscribeOn (as well as the observeOn ) needs the Scheduler param to know which thread to run on. Let’s talk about the difference between the threads.

Scheduler.io() This is the most common types of Scheduler that are used. They’re generally used for IO related stuff, such as network requests, file system operations, and it’s backed by a thread pool. A Java Thread Pool represents a group of worker threads that are waiting for the job and reuse many times.

Observable.just("Apple", "Orange", "Banana")

.subscribeOn(Schedulers.io())

.subscribe{ v -> println("Received: $v") }

Scheduler.computation() This is quite similar to IO as it’s also backed up by the thread pool, however, the number of threads that can be used is fixed to the number of cores present in the device. Say you have 2 cores, it means you’ll get 2 threads, 4 cores, 4 threads, and so on.

Observable.just("Apple", "Orange", "Banana")

.subscribeOn(Schedulers.computation())

.subscribe{ v -> println("Received: $v") }

Scheduler.newThread() The name here is self-explanatory, as it will create a new thread for each active Observable . You may want to be careful using this one as if there are a high number of Observable actions it may cause instability.

Observable.just("Apple", "Orange", "Banana")

.subscribeOn(Schedulers.newThread())

.subscribe{ v -> println("Received: $v") }

Remember, you can also set how many concurrent threads you want running, so you could do .subscribeOn(Schedulers.newThread(), 8) to have a maximum of 8 concurrent threads.

Scheduler.single() This Scheduler is backed up by a single thread. No matter how many Observable there are, it will only run in a single thread. Think about it as a replacement for the main thread.

Observable.just("Apple", "Orange", "Banana")

.subscribeOn(Schedulers.single())

.subscribe{ v -> println("Received: $v") }

Scheduler.trampoline() This will run on whatever the current thread is. If it’s the main thread, it will run the code on the queue of the main thread. Similar to Immediate Scheduler, it also blocks the thread. The trampoline may be used when we have more than one Observable and we want them to execute in order.

Observable.just("Apple", "Orange", "Banana")

.subscribeOn(Schedulers.trampoline())

.subscribe{ v -> println("Received: $v") }

Executor Scheduler This is a custom IO Scheduler, where we can set a custom pool of threads by specifying how many threads we want in that pool. It can be used in a scenario where the number of Observable can be huge for IO thread pool.

val executor = Executors.newFixedThreadPool(10)

val pooledScheduler = Schedulers.from(executor)



Observable.just("Apple", "Orange", "Banana")

.subscribeOn(pooledScheduler)

.subscribe{ v -> println("Received: $v") }

AndroidSchedulers.mainThread() Calling this on observeOn will bring the thread back to the Main UI thread, and thus make any modification you need to your UI.

observeOn

The method subscribeOn() will instruct the source Observable which thread to emit the items on and push the emissions on our Observer . But if it finds an observeOn() in the chain, it switches the emissions using the selected scheduler for the remaining operation.

Observable.just("Apple", "Orange", "Banana")

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe{ v -> println("Received: $v") }

Usually, the observing thread in Android is the Main UI thread.

Transformers

With a transformer, we can avoid repeating some code by applying the most commonly used chains among your Observable , we’ll be chaining subscribeOn and observeOn to a couple of Observable below.

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)



Observable.just("Apple", "Orange", "Banana")

.compose(applyObservableAsync())

.subscribe { v -> println("The First Observable Received: $v") }



Observable.just("Water", "Fire", "Wood")

.compose(applyObservableAsync())

.subscribe { v -> println("The Second Observable Received: $v") }



}



fun <T> applyObservableAsync(): ObservableTransformer<T, T> {

return ObservableTransformer { observable ->

observable

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

}

}

The example above will print:

The First Observable Received: Apple

The First Observable Received: Orange

The First Observable Received: Banana

The Second Observable Received: Water

The Second Observable Received: Fire

The Second Observable Received: Wood

It’s important to keep in mind that this example is for Observable , and if you’re working with other Emitters you need to change the type of the transformer, as follows.

ObservableTransformer

FlowableTransformer

SingleTransformer

MaybeTransformer

CompletableTransformer

Operators

There are many operators that you can add on the Observable chain, but let’s talk about the most common ones.

map()

Transforms values emitted by an Observable stream into a single value. Let’s take a look at a simple example below:

Observable.just("Water", "Fire", "Wood")

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.map { m -> m + " 2" }

.subscribe { v -> println("Received: $v") }

The above example will result in the following:

Received: Water 2

Received: Fire 2

Received: Wood 2

flatMap()

Unlike the map() operator, the flatMap() will transform each value in an Observable stream into another Observable , which are then merged into the output Observable after processing. Let’s do a visual representation of the difference between those:

map():

input: Observable<T>

transformation: (T -> R)

output: Observable<R>



flatMap():

input: Observable<T>

transformation: (T -> Observable<R>)

output: Observable<R>

Thanks to James Shvarts

As you can see, with a map you get T (generic type) for the value and R for the result, plain as that. And for the flatMap() you transform T into an Observable , which can have its own specific chains, including specifying its own thread.

Let’s use the same example from the map() above in a flatMap() and change the thread to computation.

Observable.just("Water", "Fire", "Wood")

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.flatMap { m ->

Observable.just(m + " 2")

.subscribeOn(Schedulers.io())

}

.subscribe { v -> println("Received: $v") }

This will result in:

Received: Water 2

Received: Fire 2

Received: Wood 2

The above example simply transformed each value emitted by the Observable into separate Observable .

zip()

The zip() operator will combine the values of multiple Observable together through a specific function.

Observable.zip(

Observable.just(

"Roses", "Sunflowers", "Leaves", "Clouds", "Violets", "Plastics"),

Observable.just(

"Red", "Yellow", "Green", "White or Grey", "Purple"),

BiFunction<String, String, String> { type, color ->

"$type are $color"

}

)

.subscribe { v -> println("Received: $v") }

The above example will print:

Received: Roses are Red

Received: Sunflowers are Yellow

Received: Leaves are Green

Received: Clouds are White or Grey

Received: Violets are Purple

Notice that the value "Plastics" did not reach its destination. That’s because the second Observable had no corresponding value.

A real-world use case would be attaching a picture to an API result, such as avatar to name, so on and so forth.

The BiFunction <String, String, String> simply means that the value of the first and second Observable are both a string and the resulting Observable is also a String .

concat()

As the name suggests, concat() will concatenate (join together) two or more Observable .

val test1 = Observable.just("Apple", "Orange", "Banana")

val test2 = Observable.just("Microsoft", "Google")

val test3 = Observable.just("Grass", "Tree", "Flower", "Sunflower")



Observable.concat(test1, test2, test3)

.subscribe{ x -> println("Received: " + x) }

The above example should print:

Received: Apple

Received: Orange

Received: Banana

Received: Microsoft

Received: Google

Received: Grass

Received: Tree

Received: Flower

Received: Sunflower

merge()

merge() works similarly to concat() , except merge will intercalate the emissions from both Observable , whereas concat() will wait for one to finish to show another.

Let’s visualize this difference to make it easier.

Observable.merge(

Observable.interval(250, TimeUnit.MILLISECONDS).map { i -> "Apple" },

Observable.interval(150, TimeUnit.MILLISECONDS).map { i -> "Orange" })

.take(10)

.subscribe{ v -> println("Received: $v") }

Both of the Observable above will keep emitting the item "Apple" and "Orange” , one every 250ms and the other every 150ms, then we take(10) results. The code will result in the following:

Received: Orange

Received: Apple

Received: Orange

Received: Orange

Received: Apple

Received: Orange

Received: Apple

Received: Orange

Received: Orange

Received: Apple

As you can see, the results intercalate between each other. What would happen if instead of the merge() operator we used concat() in this situation?

Observable.concat(

Observable.interval(250, TimeUnit.MILLISECONDS).map { i -> "Apple" },

Observable.interval(150, TimeUnit.MILLISECONDS).map { i -> "Orange" })

.take(10)

.subscribe{ v -> println("Received: $v") }

The code will result in:

Received: Apple

Received: Apple

Received: Apple

Received: Apple

Received: Apple

Received: Apple

Received: Apple

Received: Apple

Received: Apple

Received: Apple

The concat() operator is waiting for the first Observable to finish emitting values before it moves on to the next Observable . But since our Observable are designed to run forever it will never move on to the next one.

To sum up, merge() intercalates emissions whereas concat() doesn’t.

filter()

Filter the values according to a set condition.

Observable.just(2, 30, 22, 5, 60, 1)

.filter{ x -> x < 10 }

.subscribe{ x -> println("Received: " + x) }

The above code will only transmit the values that are less than 10, according to our condition x < 10 .

Received: 2

Received: 5

Received: 1

repeat()

This operator will repeat the emission of the values however many times we may need.

Observable.just("Apple", "Orange", "Banana")

.repeat(2)

.subscribe { v -> println("Received: $v") }

This should output the values 2:

Received: Apple

Received: Orange

Received: Banana

Received: Apple

Received: Orange

Received: Banana

take()

The take() operator is meant to grab however many emissions you’d like. A very simple example would be:

Observable.just("Apple", "Orange", "Banana")

.take(2)

.subscribe { v -> println("Received: $v") }

The above will result in:

Received: Apple

Received: Orange

So we only took the first two emissions.

A more real-world case would be checking if we have internet connectivity, and if we do, load it from the network, if we don’t, load it from a local cache.