How to handle errors in RxSwift

How to handle errors in RxSwift

In recent days MVVM become very popular architecture design for iOS apps. Especially when RxSwift starts to gain more and more popularity. In RxMVVM most of properties are expressed by Observables.

However, Observables terminate whenever they receive error or completed events. Termination means an Observable subscription won’t receive any new message. When I started to learn Rx I didn’t realize the consequences of this rule.

Do you have problems with errors handling? Did your Observable terminate unexpectedly and your button stopped sending tap events? This is what the article is about. Enjoy reading 📚💪


Errors – The example

Mobile applications usually do some API requests when a user taps a button. Our example will cover such a case:


Tapping on success invokes fake API request with success answer. In the same way, tapping on failure fakes the error. Tapping on the buttons should increase the count number.

This is what I want to achieve represented by this diagram:

 
successTap        -s-----s--s-----s---------->  
failureTap        ----f---f-----f----f------->  
buttonTaps<Bool>  -T--F--TF-F---F-T--F------->  
response          --V--E--VE-V---E-V--E------>  
(using flatMap)  

where:  
's' and 'f' - success or failure button tap  
'T' and 'F' - true or false  
'V' - success response  
'E' - failure response (error) 

Coding time – the #1 attempt

Let’s write some code. You need to map() tapping on the success button as true event and map tapping on the failure button as false. Next, you have to merge() them into single Observable:

In case this is your first time with Rx and merge(), map() or flatMap() seems strange, read Thinking in RxSwift first. I’m describing there how to think in Reactive way and how the basic operators works 🙂

Honestly speaking, tapping on success will indeed increase the success count. However, as soon as you tap the failure button the whole Observable chain will dispose itself. Since now, tapping on success won’t increase the success count anymore 🙀.

Why? you ask.
When performAPICall fails it returns an error event (the same as a real API call does). Since we use flatMap all the nexts and errors from the inner Observable are passed into the main sequence.

As a result, the main Observable sequence receives an error event and it also terminates 💀⚰.


RxSwift & errors – How to handle them?

Sometimes errors are what you expect to happen. Take a login form as an example. You expect the server to return an error if a password doesn’t match a given e-mail. It’s an expected error, and god, this is good the error comes! 👌

If an error isn’t the exception it shouldn’t end the Observable sequence. To make that happen, your API calls should returns Observable<Result<T>>. Having a Result<T> as next event won’t terminate the main Observable sequence.

However, there is simpler approach. RxSwiftExt provides materialize operator. It transforms Observable<T> into Observable<Event<T>> which has 2 additional operators:

  • elements() which returns Observable<T>
  • errors() which returns Observable<Error>

Thanks for those two observables it is possible to handle API errors as you would like to:

😱 – the performAPICall() is called twice

Above solution works as we expect, however, there is one bug inside. Whenever you press any of the buttons, the performAPICall() is called twice. You would have to put a breakpoint in performAPICall() to notice that.

You may say it is not a big deal in our sample, but in real life invoking one method 2 times would send 2 requests to the server which is bad. To fix that you need to use share() operator in the result Observable. The rest is unchanged:

Where to go from here?

When you use RxSwift extensions to feed the UI, handling errors is not as simple task as you may first think of. Error event breaks the Observable, even if the error comes from an inner flatMap.

Usually, you want to notify a user about errors. To do so, you have to treat them as something expected to happen, not like an exception. I recommend to use materialize() from RxSwiftExt. However, don’t forget to use share() 🙂. You don’t want to send 2 requests to the API 😉

You can find the project sample here.

If you want to read more about share() operator there is a great article about it.

One of the GitHub issues says more about errors and the idea there is no such thing as universal error. The talk is eye-opening. I think it is worth to read id.

Do you like the article? Please share it by clicking on buttons below. Cheers!

References

Title image – dribbble.comArtur Martynowski @ All in Mobile