Swifty Firebase APIs @ Ka-ching — part 1

Adding support for Codable types

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:

Background

The Firebase Realtime Database is an excellent tool for building apps. There are plenty of articles covering some of the advantages of using Firebase, so this post will not go into details about that, but instead assume that you have some prior knowledge of the technology.

The iOS APIs for Firebase are currently written in Objective-C, and although the APIs present themselves nicely in Swift, this does mean that we cannot take advantage of Swift-only features like Codable support out-of-the-box.

This post (and the following few) will explore the possibility of adding nice and ‘Swifty’ extensions to the Realtime Database API.

Overview

Here is an overview of what the posts will cover:

  1. (This post) Adding support for Codable types.
  2. Adding type-safe paths to data in your database
  3. (Yet to come) Adding RxSwift extensions to provide data as Observables

Motivation

The motivation for this first post is to allow you to consider the data stored in the Realtime Database as model objects instead of as pure data.

For the second post we will get rid of the error prone use of String paths to data.

The third post is motivated by the fact that there is a pretty big overlap in how the world is perceived through Rx glasses and how the Realtime Database APIs work. Namely the fact that data is in both concepts percieved as values that change over time. Let us take advantage of this by combining the concepts.

TL;DR

The goal of this post is to be able to use Codable model types with the Firebase Realtime Database APIs like this:

Codable

Many other articles explain Swift’s Codable in greater detail. Suffice it to say here, that annotating your model types with Codable conformance provides an elegant and convenient way of allowing your types to be serialized into various formats.

One of these very common formats is naturally JSON. Support for decoding from and encoding to JSON is provided through the types JSONDecoder and JSONEncoder respectively.

Unfortunately, these two types consume and create JSON in the format of Data instances containing the String representations of the JSON — while the Firebase RTDB APIs create and consume JSON ‘structures’. Basically this means nested values of type Dictionary, Array and ‘primitive’ types like strings, numbers and booleans.

This means that there is a gap between the two APIs. One way of bridging this gap would be to take the Data output from JSONEncoder and deserializing it into a structure using the JSONSerialization API. But this doesn’t feel quite right since it basically needs you to do extra work.

Fortunately, Swift is Open Source, and the current implementation of JSONEncoder and JSONDecoder almost already does what we want.

So here is a small recipe for making your own Firebase RTDB-compatible Encoder and Decoder pair.

Cloning JSONEncoder.swift

Create a new project in Xcode. Let it be a Cocoa Touch Framework.

Find the JSONEncoder.swift file in the github repo for your version of Swift. In this example we’ll go with Swift 4.1

  1. Go to the apple/swift repo and navigate to stdlib/public/SDK/Foundation
  2. Copy JSONEncoder.swift as well as Codable.swift (which contains a helper function for creating error values needed by JSONEncoder)
  3. Rename JSONEncoder to StructureEncoder (the name is borrowed from Itai Ferber in a Swift forums discussion)
  4. For the encode function, replace the return type Data with Any, remove the JSONSerialization step and return topLevel directly.
  5. Similarly, rename JSONDecoder to StructureDecoder
  6. In the decode function, replace the input from json: Data with from structure: Any and call _decode directly on the structure instead of deserializing the json data first.
  7. Be sure to import Foundation in both the renamed StructureEncoder.swift and Codable.swift
  8. Remember to add attribution to the apple/swift project in your library or app.

Now you have a functioning encoder / decoder pair that can be used together with the RTDB APIs!

Add the Result type to your project

This is of course based on your own preference, but there are good reasons for modelling the input to an asynchronous callback using a type like the popular Result-type implemented by antitypical — instead of as a pair of optional version of your model type, and an optional Error .

This concept is explored in depth by the excellent Stephen Cellis and Brandon Williams on their point free episode about Algebraic Datatypes.

Improving the Realtime Database APIs

Ok, after quite a bit of setup, we are finally ready to start extending the APIs.

Let’s start by adding a convenience to DataSnapshot to allow decoding the data into a generic type that conforms to Decodable:

We have created an error type, DecodingError to hold the exact errors that can occur while using the API.

When decoding, we create an instance of our StructureDecoder. Later we can add a way of configuring the decoder with a set of global options, but for now we are just using the defaults.

With decoded in place, we can now start overloading functions on the DatabaseReference type. But since these functions actually live on the super class DatabaseQuery, let us extend that instead:

Encoding can be implemented as simple as:

For encoding we can easily see the benefits of the typed API:

https://gist.github.com/mortenbekditlevsen/cd351f36e61cd04eab3c7eaa53b0bf25

Conclusion

With just a little groundwork, we can now use the Firebase Realtime Database APIs together with Codable types. The APIs are still a bit clunky to use since you need to supply the generic type argument inside of the callback function signature.

We will deal with this bit of clunkyness later. Until then you can imagine the observeSingleEvent returning some kind of Future type, so that the type can be inferred by the assignment — or when Swift support for async / await lands (in Swift 6, perhaps?), observeSingleEvent could be an async function used like this (assuming that async functions are also throwing):

do {
let product: Product = await ref.observeSingleEvent(of: .value)
} catch {
// Handle error
}

Considerations

The downside of using JSONEncoder.swift from the swift repo is that it needs to be maintained as swift evolves. There has been talks in the Swift forums about adding a StructureEncoder and StructureDecoder pair to Swift, but from the discussions it appears that they would not get JSON ‘semantics’, so likely no support for automatic encoding and decoding of keys to snake case and the like.

At our company we have maintained a version of the StructureEncoder.swift through 3 swift releases, and it has not been a very big deal to maintain it so far.

Github

We created a small repo to demonstrate the changes to JSONEncoder.swift and the extensions to the Firebase API.

Who are we

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. :-)