Building Breather (Part 3 — Bonus): Combining network requests with RxSwift’s zip and switching tuples with Swift’s switch

Breather is an open-source iOS app that shows the weather, air pollution and asthma conditions around you.

Alexandros Baramilis
7 min readMay 4, 2019

To raise awareness about air pollution and practice my iOS skills at the same time, I’m building an open-source app where I apply some of the latest knowledge I gain about iOS, Swift and programming. Since there is a lot of information to digest, I’m splitting it into small manageable parts:

ReactiveX’s zip operator

Every time I’m like: “I’m not gonna do a Bonus section this time”, but there’s always something extra left to write about 😅

I didn’t give a lot of love to the Air by Propeller API in the previous sections, so it’s time to bring it into the fold.

What I want to do here, is when the view requests a refresh (via any of the methods described previously), I want to fire two network requests simultaneously (one to AirVisual and one to Propeller) and when both of them return, I want to update the UI.

ReactiveX’s zip operator was made for this.

ReactiveX’s zip operator (source)

Imagine that in the above marble diagram, the first Observable is the request to AirVisual API and the second Observable is the requests to Propeller API. By zipping the two Observables, we only observe an event when both Observables have emitted a new event.

A detail that is aptly represented in the above diagram, is that the zipping applies by index. So the first item of the first Observable will be zipped to the first item of the second Observable, the second item to the second item, the third to the third, and so on. So when the first Observable emits 3, you might think that it will be zipped to to D, but it’s actually zipped to C, because both 3 and C are at the 3rd index.

(Zip is similar but different to ReactiveX’s combineLatest operator, which emits an event whenever any of the two Observables emit a new event, using the latest value from each Observable.)

Setting up the Air by Propeller API with Moya

The same way we did it in Part 3 for AirVisualAPI, we setup the PropellerAPI client with Moya.

Air by Propeller API setup with Moya

Add another MoyaProvider to our dependencies.

And we’re ready to start zippin’.

Combining network requests with RxSwift’s zip operator

(EDIT: I’ve come up with a more elegant that the one written here. If you want to save time, skip to the end of the article.)

There are a few things going on here:

(If you want to refer to what we had before go to Part 3)

  • In line 146: Whenever the view requests a refresh, viewDidRefreshSubject will emit and we fire a next event with a true element on the isLoadingSubject to indicate to the view that we have started loading. I move this out of the main chain here because I didn’t want to duplicate the code for each request.
  • In lines 152 & 159: These are the two requests, same as before, flatMapping the RxMoya request and materializing it so the sequence doesn’t terminate if we get errors. Again, I moved this out of the main chain to simplify the zip code and so that we can fire them simultaneously. Also, don’t forget that these requests are Observables and don’t get dispatched until someone subscribes to them.
  • In line 166: Here, we perform the zip, emit a next event with a false element on the isLoadingSubject to indicate to the view that the loading has finished and then we subscribe to the combined Observable. Now, whenever the view requests fresh data, the requests will be fired and when both of them return we observe a next event.

Swift switching with tuples

Because we materialized and zipped the Observables above, the elements that we observe in subscribe’s onNext handler are of type:

(Event<Response>, Event<Response>)

which is a tuple of events containing Moya Response objects.

Each Event has three cases (next, error and complete), so both of them combined have 9 cases. Imagine the ugly code doing that with if statements. Thankfully, Swift’s switch is very powerful and allows us to work with tuples too. What’s even cooler is that Xcode’s compiler will also find missing cases for you, to make sure that you’re handling everything.

So we can condense that into some nice and readable code:

Switch with tuples
  • If both requests are successful, we have two next events and we just print out the responses for now.
  • If either one of them has an error, we send it through the errorSubject. This also takes care of the case that both of them have an error. In that case we only show the first error. We could combine both errors into one but it’s not really worth it.
  • We don’t care about all the other cases so we can just break.

Now if we run the app, we should see in the console:

weatherAndPollutionResponse: Status Code: 200, Data Length: 354

asthmaResponse Status Code: 200, Data Length: 151

If we keep pulling to refresh, these two should appear simultaneously.

If we simulate an error on the first Observable by say, mistyping the URL: “https://api.oops.com/v2", we should see the alert:

Error on the first Observable

If we simulate an error on the second Observable by say, forgetting to type a t on https: “htps://open.propellerhealth.com/prod", we should see:

Error on the second Observable

If we simulate an error on both Observables by say, going offline, we should see:

Error on both Observables

And if we recover by going online, we should see again:

weatherAndPollutionResponse: Status Code: 200, Data Length: 354

asthmaResponse Status Code: 200, Data Length: 151

Try to say out loud as fast as you can:

“Switching tuples with Swift’s switch.” 😂

EDIT: Combining network requests with RxSwift’s zip operator (a better solution)

I’ve reduced the previous 37 (and a bit messy) lines of code into 19 (and more readable) lines.

It’s also much easier to explain the code now:

  • In line 146: The AirVisual API request Single
  • In line 147: The Propeller API request Single
  • In line 148: The combined request (using zip)
  • In line 150: Whenever the view refreshes, we start loading, do a flatMap where we return the zipped Observable materialized, stop loading and subscribe.

By performing materialize() on the zipped Observable, we get next elements of type Event<(Response, Response)> which is easier to handle than the (Event<Response>, Event<Response>) tuple we had before and doesn’t require switching tuples either.

Also thanks to materialize, we only get next events, so the chain doesn’t terminate when the requests complete or if we get an error. This way we can keep refreshing the UI.

Inside those next events we have some wrapped or inner events (what I call materializedEvent in the code). Those can be:

  • next events carrying our two Response objects (if and when both requests are successful)
  • error events carrying an error (this can be an error from any of the zipped Observables, prioritising the first one if both have an error)
  • completed events that we just ignore (these are carried from the Singles that emit a completed event after each next event, but because we wrapped them with next events using materialize, our chain doesn’t terminate)

--

--