Leverage the best of RxJava with Kotlin. Illustration done by Nimble design team.
When developing Android applications with the support of RxJava, you may have used the debounce operator for your Search EditText. It is very helpful in avoiding the overhead of API calls for each user keystroke.
However, in some cases, we don’t want to debounce all of the emitted items. We only want to debounce some specific items (based on their values) while leaving others untouched.
For example, given the stream Observable.just(1, 2, 3, 4), let’s say we want to debounce only odd numbers, while having even ones being emitted immediately. How can we do that? Let’s discover it in this post.
Let’s implement a custom RxJava operator debounceIf by leveraging Kotlin extension method:
The above method will debounce everything! We need to figure out a way to have an if-else statement behaviour in RxJava. To achieve that, we will split the main stream then merge the results back.
Removing the redundant this, and appending the debouncing part:
The above method looks promising, let’s try it out!
Back to our example (given the series of 1–2–3–4), we want to debounce only odd numbers, while having even ones being emitted immediately:
The output result is: 2 4 3
2 and 4 got emitted immediately while 3 was delayed for 2 seconds before eventually being emitted. Bravo!! We have achieved what we wanted to.
But wait, something isn’t quite right here!
In a real-world scenario, when the user input changes from 1 to 2, then from 2 to 3, then 3 to 4 within a short period of time, only the final number (number 4) should be taken into account. We should ignore all of the previous inputs because they are no longer valid. It is the original intention of debounce, isn’t it?
The previous method was neat but it’s NOT a perfect solution, since it causes side-effects. There’s a tricky pitfall you may encounter when using it.
A Better Solution
Let’s change the implementation a bit:
We simply swapped filter() and debounce()in the first stream. And it works correctly:
The output result is: 2 4
2 and 4 got emitted immediately (due to the second stream) while 3 was delayed for 2 seconds (due to the first stream). However, number 4 came right before 3 being emitted (in less than 2 seconds), thanks to the debounce nature of Rx, 3 was ignored and 4 was debounced instead.
After 2 seconds, however, 4 didn’t have a chance to be emitted, because the first stream already muted it by using the filter operator.
By simply swapping the debounce and filter operators, we have successfully maintained the property of debounce (ignoring previously emitted items which came too quickly)
A Closer Look
The above method works correctly in terms of the output (downstream), but it still has a problem with the upstream. (Credit to Dávid Karnok, an RxJava Code Guru, who has spotted it 👏)
The printed output is: Hello World Hello World
As we can see that the upstream was subscribed 2 times! Depending on a specific situation, it could mean double API requests, or double database transactions!
It is due to the way RxJava handles cold observable source. To understand the difference between hot and cold observables, head over to this post.
To avoid doubling the effect of the upstream, we turn it into a hot observable by using publish() method.
It’s now working properly.
The printed output is: Hello World
By publishing the observable source and applying a few tricks to it, we have successfully defined a new debounceIf operator. It is quite useful in a number of cases, especially when dealing with EditText.