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:
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 next
s and error
s 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 returnsObservable<T>
errors()
which returnsObservable<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.com – Artur Martynowski @ All in Mobile