Application Architecture for SwiftUI & Firebase

SwiftUI: Mapping Firestore Documents using Swift Codable

Peter Friese
May 8, 2020 · 5 min read

Learn how to easily map Firestore data in Swift

Last time, we looked at how to connect a SwiftUI app to a Firebase project and synchronise data in real time. If you take a look at the code we used for mapping from Firestore documents to our Swift model structs, you will notice that is has a number of issues:

  • It is rather verbose — even for just the few attributes we’ve got!
  • The code makes some assumptions about the structure of the documents, such as the attribute types
  • Things might start breaking if we change the structure of the documents or the struct holding our model

So in this post, I’m going to show you how to make your mapping code more concise, less error-prone, and more maintainable by using the Codable protocol.

What is Codable?

The Codable protocol is Apple's contribution to a standardised approach to encoding and decoding data. If you've been a Swift developer for a while, you might recall that, before Apple introduced Codable in Swift 4, you had to perform data mapping from and to external representations either manually or by importing third-party libraries. None of this is required anymore, thanks to Codable.

For a long time, Firestore lacked support for Codable, and the GitHub issue asking for adding support for object serialization in Firestore (#627) might be one of the most popular / upvoted issues of Firebase iOS SDK so far.

The good news is that, as of October 2019, Firestore provides support for Codable, and it's every bit as easy to use as you might hope it is. It essentially boils down to three steps:

  1. Add Firestore Codable to your project
  2. Make your models Codable
  3. Use the new methods to retrieve / store data
  4. (Bonus!) delete all of your existing mapping code

Let’s look at these a little bit closer.

Prepare your project

As Firestore’s Codable support is only available for Swift, it lives in a separate Pod, FirebaseFirestoreSwift - add this to your Podfile and run pod install.

Make your models Codable

Import FirebaseFirestoreCodable in the file(s) holding your model structs, and implement Codable, like so:

As we want to use the Book models in a ListView, they need to implement Identifiable, i.e. they need to have an id attribute. If you've worked with Firestore before, you know that each Firestore document has a unique document ID, so we can use that and map it to the id attribute. To make this easier, FirebaseFirestoreSwift provides a property wrapper, @DocumentID, which tells the Firestore SDK to perform this mapping for us.

If the attribute names on your Firestore documents match with the property names of your model structs, you’re done now.

However, if the attribute names differ, as they do in our example, you need to provide instructions for the Encoder / Decoder to map them correctly. We can do so by providing a nested enumeration that conforms to the CodingKey protocol.

In our example, the name of the attribute that contains the number of pages of a book is called pages in our Firestore documents, but numberOfPages in our Book struct. Let's use the CodingKeys to map them to each other:

It is important to note that once you use CodingKeys you'll have to explicitly provide the names of all attributes you want to map. So if you forget to map the id attribute, the ids of your model instances will be nil. This will result in unexpected behaviour, for example when trying to display them in a ListView. Check out Apple's documentation for a more detailed discussion of Codable.

Fetching data

Now that our model is prepared for mapping, we can update the existing mapping code on our view model. At the moment, it looks like this:

Time to delete some code and simplify this!

We can replace the mapping code in the closure with a much simpler version:

As you can see, we got rid of the entire process of manually reading attributes from the data dictionary, performing typecasts, and providing default values. Our code is a lot safer now that the SDK takes care of this for us.

All of this is thanks to the data(as:) method, which is provided by the FirebaseFirestoreSwift module. Doesn't it feel great to remove all this code?

Writing data

So we’ve covered mapping data when reading it from Firestore — what about the opposite direction?

It turns out that this is almost as simple as reading data — let’s take a quick look. To write data, we can use use addDocument(from:) instead of .addDocument(data:):

This saves us from writing the mapping code, which means a lot less typing, and — more importantly — a lot less opportunity to get things wrong and introduce bugs. Great!

With the basics under our belt, let’s take a look at a couple of more advanced features.

We already used the @DocumentID property wrapper to tell Firestore to map the document IDs to the id attribute in our Book struct. There are two other property wrappers you might find useful: @ExplicitNull and @ServerTimestamp.

If an attribute is marked as @ExplicitNull, Firestore will write the attribute into the target document with a null value. If you save a document with an optional attribute that is nil to Firestore and it is not marked as @ExplicitNull, Firestore will just omit it.

@ServerTimestamp is useful if you want need to handle timestamps in your app. In any distributed system, chances are that the clocks on the individual systems are not completely in sync all of the time. You might think this is not a big deal, but imagine the implications of a clock running slightly out of sync for a stock trade system: even a millisecond deviation might result in a difference of millions of dollars when executing a trade. Firestore handles attributes marked with @ServerTiemstamp as follows: if the attribute is nil when you store it (using addDocument, for example), Firestore will populate the field with the current server timestamp at the time of writing it into the database. If the field is not nil when you call addDocument() or updateData(), Firestore will leave the attribute value untouched. This way, it is easy to implement fields like createdAt and lastUpdatedAt.

Where to go from here

Today, you learned how to simplify your data mapping code by using the Codable protocol with Firestore. Next time, we're going to take a closer look at saving, updating, and deleting data.

Thanks for reading, and make sure to check out the Firebase channel on YouTube.

Firebase Developers

Tutorials, deep-dives, and random musings from Firebase…

Sign up for The Firebase Developers Quarterly

By Firebase Developers

A summary of what has happened on the Firebase Developers publication in the past quarter. Sent once a quarter. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Peter Friese

Written by

Google Developer Advocate with the Firebase team 🔥

Firebase Developers

Tutorials, deep-dives, and random musings from Firebase developers all around the world. Views expressed are those of the authors and don’t necessarily reflect those of Firebase or its parent companies.

Peter Friese

Written by

Google Developer Advocate with the Firebase team 🔥

Firebase Developers

Tutorials, deep-dives, and random musings from Firebase developers all around the world. Views expressed are those of the authors and don’t necessarily reflect those of Firebase or its parent companies.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store