While working with Reactive Programming, network requests are most of popular logic we often handle. Today, we’ll discuss how to handle a sequence of network request correctly, fixed some common mistakes as well as proposing some common approaches that most of developers often use in real projects nowadays. In this post, we’ll use RxSwift to demonstrate the code, since ReactiveX is a cross-platform then RxJava, RxKotlin or even Combine can also involve .
First of all, please download this Xcode project and make sure it builds successfully, we’ll use it to demonstrate use cases in this article.
Let’s start with a common requirement, we have an UIButton which will trigger a network request to fetch the data from server. We’ll display the data as an Observable’s output by an UITextView. You can run the project which is downloaded to see the UI & flow:
First, we’ll simulate a network request by return a random number with 1 second of delay:
Then we’ll integrate this into button’s action, subscribe & print out the result to textView:
We’re using the flatMap operator to map each “tap” even from button into a fetchData observables then flatten their events (random numbers) into a Single observable. The GIF image above display the result as: 563, 412, 430, 719. This diagram bellow visualizes what happen under the hood:
Everything is working fine now. As you can see the network requests equal to the number of tap you touch to button. In some scenarios, we only care about the final request, for example while searching by a keyword in real time:
For the “RxSwift” keyword, if we use flatMap operator, we have to call 7 network requests for keywords: R, Rx, RxS, RxSw, RxSwi, RxSwif, RxSwift. Back to our example with button & random number, now requirement is changed to display the final result only, if previous requests are in processing then just cancel them. FlatMapLatest is a best operator to use in this case, it does the same thing with flatMap but only subscribe to the latest stream. Before jumping to the code, let’s see the result first:
No matter how many times we touch the button, only 1 network request is executing at a moment, just change operator to flatMapLatest:
To understand why 977, 819, 668 are printed out after 10 taps on the GIF above, this diagram might help:
We have just optimized the network requests in some required scenarios but everything is under a happy case where requests are success. Now let’s try to simulate a network error:
If we turn on the switch on UI to simulator a network error, our tap functionality is no longer working! No network requests are making after that 🙈🙈🙈
Have you faced this issue before? For me, yes! In the first time to work with RxSwift, I even thought it was a bug LOL. Now just recall to the basic theory, when an Observable/Sequence is terminated? From RxSwift repo:
completedevent is received, the Observable/Sequence cannot produce any other elements.
Even though btn.rx.tap is a Driver. From RxCocoa repo:
This is the most elaborate trait. Its intention is to provide an intuitive way to write reactive code in the UI layer, or for any case where you want to model a stream of data Driving your application.
• Can’t error out.
• Observe occurs on main scheduler.
• Shares side effects (
share(replay: 1, scope: .whileConnected)).
A driver never throw any errors so I assumed that couldn’t be terminated either but I was wrong. It is potentially terminated by other errors in its sequence. This diagram explains why the tap functionality is stop working when a network error occurs:
So to let the sequence be still working in this case, we have to catch the error. I figured out that if the child observable inside flatMap or flatMapLatest operators send a completed event (cause the Observable is terminated as error) that won’t terminate the whole sequence. It is safe to catch the error to an Observable.Empty() in this case:
Now, for any errors won’t dispose the whole sequence:
1. We define an action that transforms a Bool as an input value to a Int as an output value.
2. We handle the elements from action’s outputs (Random integers in this case)
3. We handle the errors from action. Action by itself catch the errors to let the action continue working & expose the errors either like the way we handle by the do operator on the above session.
4. We can also handle the executing state here to show a loading HUD on UI as an example.
5. We set the input for our action by binding the tap event from UIButton and get the input from a UISwitch
Another approach is using RxActivityIndicator or RxErrorIndicator to track the executing & error states of sequence:
1. Instantiate trackers.
2. Handle the executing & error states by using trackers instead if do operator. The errorIndicator should catch the error & expose them to let current sequence continue working.
3. Subscribe to show the loading state.
4. Subscribe to show the error state.
The RxErrorIndicator is inspired from RxActivityIndicator, the idea & code implementation is quite simple so I don’t publish it here to let you take a small challenge 😜
These are 2 popular approaches to handle the executing & error states of network requests in RxSwift. Are you using another approach? Feel free to share it by a comment 🤗