Swifty Firebase APIs @ Ka-ching — part 3

Morten Bek Ditlevsen
Swift2Go
Published in
5 min readAug 22, 2018

Firebase and RxSwift — a marriage made in heaven

UPDATE, OCTOBER 14th, 2018:

Since writing this article, the concepts described in these blog posts have been made into two Open Source frameworks, which can be found here:

This post is a continuation of the 3 (or 4 or 5?) part series.

In the first post, we added Codable support to the Firebase APIs:

In the second post, we added Type Safe paths using Phantom Types which gave us strongly typed paths and excellent API ergonomics:

In this post, we are going to add support for RxSwift. If you want to learn more about RxSwift, please refer to the official github repo, which has excellent examples of use cases.

The only further motivation I will present here is that there is an huge overlap in the mindset of the Firebase Real-Time Database and Rx: the RTDB allows the user to subscribe to data as it changes over time. One of the main purposes of Rx is exactly to provide primitives to model data as it changes over time.

As I see it, Rx is the perfect way to consume the Firebase RTDB APIs. For instance, observing changes for a location in Firebase using the native APIs require the user to keep track of a subscription token that is returned when subscribing. In Rx we can wrap this token in the subscription so that the Firebase observer is removed when the subscription is disposed.

But all this is probably easier to grasp when we start coding!

Adding an Rx extension

Let us start by adding an Rx extension to a DatabaseQuery (which is a super type of DatabaseReference) for convenience. The pattern for adding Rx extensions can be found in the RxSwift repo — for instance in the RxCocoa extensions. It looks as follows:

Extending Reactive with the constraint on Base means that the type DatabaseQuery gets a .rx property that can be used to call the extension functions:

let obs: Observable<DecodeResult<Configuration>>
= ref.rx.observe(eventType: .value)

For the wrapping of a single observation we return a Single<T> type from RxSwift. Single represents an observable that can have at most one element. This means that it either always completes (returning that single element) or fails with an error. In that respect, a Single can be compared to a Future or a Promise¹.

For the wrapping of continuous observations we return an Observable<DecodeResult<T>> . When creating an Observable you need to return a Disposable that will be used to keep track of the life time of a subscription to an Observable. The closure passed to Disposable.create will be called when a subscription is disposed, and this provides a convenient way to remove the actual Firebase observer.

You may wonder why we use different generic types T and DecodeResult<T> for the Single and the Observable respectively. The reason for this has to do with the way that Observables work. For the Observable case: as soon as an observable has an error, the signal ends. This means, that if we treated errors (like network errors, missing data errors) like Observable errors, then the signal would fail upon missing network. We might not be interested in such a behavior, so instead we let our signal contain a sequence of DecodeResult<T> — then the user has more freedom to interpret the errors as they see fit. We will add convenience functions to ‘unwrap’ the result type below. For the case of the Single we do not have the issue of the signal terminating after the first error, because the Single always terminates after the first value. So in this case there is no reason to keep the DecodeResult wrapper.

Extending the FirebaseService

Let us extend our FirebaseService from the last post to add support for type safe paths:

So here we replace all the observe functions to return an Observable (or a Single) instead of taking callback closures as we did in the previous post.

Filtering DecodeResults

If you like to live your life dangerously, or have no sound way to handle errors, you may wish to filter away the errors, or have them logged, while only actually handling the success cases where model entities are received and parsed successfully. For this purpose we could create overloads of the observe functions. But as the amount of wrapper functions could grow in the future, we could instead add a generic way of filtering Observable<DecodeResult<T>> signals.

This can be done by a constrained extension on Observable, but we cannot constrain on DecodeResult since this is itself a generic type. What we can do is create a protocol and use this to create our constrained extension:

To explain the code above: We add a protocol ResultProtocol to use for our constrained extension. Then we conform Result to this protocol. Now we can create the extension constrained to Observable types containing ResultProtocol-conforming elements.

One function, filtered(), simply throws away all error values in the Observable stream while filtered(handler:) gives you an opportunity to intercept error values — for instance for logging. If you have a Singleton’ish logging concept, you could use this to create your own small extension that filters success values and logs errors.

Consuming the Rx’ified API

As an example of consuming the updated API, I have created a small ViewModel — in a style inspired by Kickstarter´s open sourced iOS code.

Pass an instance of the ViewModel (as an anonymous ViewModelType) to your ViewController, create a few bindings, and you’re done!

Future work

There are more areas to explore here, so I think that I will have to write a few more posts. Ideas for future directions are:

  • Adding higher level abstractions to our API (improved observation of collections of objects)
  • Auto-generating boilerplate for our Path modelling of the firebase database hierarchy.
  • Creating similar wrappers for Firestore, so that migration in the client will be as easy as swapping out the concrete implementation.
  • Caveats when using Firebase Real-time database. There are quite a few. Caveats using RxSwift — and caveats using Codable (especially using key coding and decoding)
  • Applying Tagged Pointers instead of the current String based keys in the API.

Let me know what you would like to hear!

Github

As always, the sample code can be found on github — on a branch named rxswift.

About the author

My name is Morten Bek Ditlevsen, and I work for the Danish company Ka-ching (website in Danish).

At Ka-ching we are building a world class Point-of-Sales system for iOS. Currently we are B2B only, but we will keep you posted once we enter the App Store. :-)

Feedback

If you have any questions, comments or need clarification for some of the examples, please don’t hesitate to ask!

Foot notes

  1. with the noticable exception that Singles do NOT cache their result, and thus may be triggered multiple times if multiple places in the code subscribe to the result.

--

--

Morten Bek Ditlevsen
Swift2Go

iOS dev since the unofficial tool chain. I love Swift and work at the awesome start-up: Ka-ching