Within our app written in Swift and using SwiftUI, we needed a way to read our data from Google Firestore into the app, and then write some data back into Firestore.

Initially, I followed this guide from the Firestore documentation, although it involved a lot of mapping dictionary key/values to the type I was after. Writing data was much the same. For each property in my Swift class I had to specify the name of the corresponding Firestore field, and then assign the appropriate value to the field. I knew there must be a better way.

Fortunately, the Firestore documentation recognises this problem and offers a way to bring your document into a Swift class, and allow you to use it as you would any other piece of data. This is through the use of the Pod FirebaseFirestoreSwift , which adds an extension method to the document.data() call so you can specify what type the data is coming in as. There is also a similar method on the document.setData() method for converting your Swift object back to a Firestore object.

However, I wasn’t sure how this would work where the data types weren’t simple String to String or Number to Int mappings. Specifically, there were five situations I wasn’t sure about (in order of least to most complicated):

  1. Arrays/lists
  2. Complex/custom objects
  3. Where the names of the fields didn’t match
  4. Enums
  5. Other data transformations

How do I convert my Swift object to Firestore and vice versa?

The most basic example is when your object only has simple data types.

A simple Firestore object with a string and a number

The corresponding Swift struct could look like this

A simple Swift struct with a string and an integer

To get your Firestore object into Swift, you need to use the .data(as: <type>) method. In the scenario below, I am getting all the documents in the Firestore collection websites and putting them into a property called self.websites

Bringing Firestore data into Swift

A few things to note:

  • Line 12 is where I call the document.data(as: <type>) method. I pass the type I want to turn the document into here.
  • On line 11, I am using a flatMap . This is like a map in that it transforms every object in the array to another object. In this case I am transforming each document (which is essentially a dictionary or key/value pairs) to my Website struct. Except, if a result returns nil, which happens when it is unable to convert it into the specified struct, it will remove it from the array.
  • An equivalent way of doing this would be to first apply a filter on documents to remove any items which weren’t able to be converted into the Website struct. Because Firestore itself doesn’t require each document in a collection to look the same, it is possible to have documents which can’t be converted to the struct you have specified.

There is also the opposite method, which works much the same way but for saving data to Firestore. Note I am setting just the one document here.

All the code we’ve looked at won’t work yet. We are missing one small ingredient.

We need to let Swift know how to convert the document, at this point just a dictionary, into the struct we would like. For this simple example, this is really easy. We only need to do three things to our struct.

The Website struct, now codable and identifiable

  1. Conform our struct to Identifiable . This requires it to have an id property, which we will add…
  2. Add an id property and annotate it with @DocumentID . This is where Firestore will store the id of the document itself. This is required and is useful for saving the right document back to Firestore.
  3. Conform our struct to Codable . This is the essential step that allows Swift to convert to and from our struct to Firestore.

Codable

Codable is the awesome protocol (made up of two protocols: Decodable and Encodable) that adds two key functions:

  1. An init() method, which allows us to create the struct from the dictionary that Firestore returns. This is the decoder.
  2. A method which allows us to convert our struct back to the dictionary format that Firestore is expecting. This is the encoder.

Each of these methods separately comes from the Decodable and Encodable protocols respectively. If you have a struct that you only ever need to retrieve from Firestore, but never save back to Firestore, you could simply conform to the Decodable protocol.

Fortunately Codable is quite smart, and if you have a struct like ours where there are only simple types like String, Int or Bool, that is all you need to do. Our code will now work.

1. What if my object has an array?

Fortunately, arrays are supported just as well as a standard type. See line 5 below.

Swift struct with an array of strings

This will convert an array of strings from Firestore straight into an array of strings in Swift. No extra code required.

2. What if my object has custom data types?

A common scenario is supporting custom data types you’ve written yourself. For example, my website could have an author attached to it, which is its own data type. Let’s view how this would be represented in Firestore.

Luckily implementing this in Swift is also trivial. We just need to define another struct containing the fields we need in our custom data type. Importantly, this also needs to conform to Codable. In fact, you can nest custom objects as deep as you like, so long as every struct conforms to Codable. Let’s see this in action:

As you can see, I’m using the custom Author type in our Website struct. You’ll notice Author doesn’t need to conform to Identifiable , as we are only keeping track of one single author, and author isn’t a Firestore document — it’s simply a field within our website.

You can also make arrays of custom objects in the same way as you’d make an array of strings.

#ios-app-development #ios #firestore #swift #codable

Firestore Objects to Swift Structs and Back, With Complex Types, Enums and Arrays
2.25 GEEK