Learning and using Firebase | /var/log/share

Learning and using Firebase

I’ve been working on my side-project for quite some time now. If you read my earlier blog post, I mentioned how I wanted to build an iOS app. I choose to build one natively allowing me to learn something new. I have no prior knowledge concerning iOS app development, so all of it is novel. More on that in a later blog post. This piece is going to be about Firebase and a rant on some of its brilliance.

An introduction to Firebase

To sum up Firebase - it’s nothing more than a swiss army knife for getting your app deployed as quickly as possible. They offer around 10+ services and its a product by Google. I am using it for some core functionality namely:

The list looks pretty huge, but all of it is offered by a single product. This makes it easy to bootstrap and as you start building more - you get familiarized with the tool. So far, I’ve enjoyed working with Firebase and top it up - documentation on Firebase is top-notch. If you are building something new, and you want all of the functionality above - I recommend using Firebase. To be fair, I did look at other alternatives before landing on Firebase. If you want to deal with any of the above functionality, the alternatives are Realm (functions as a cloud storage and database on-device) and spinning up your own service for the rest. There might be other services like Parse (which existed long back) but I don’t think there is anything like Firebase in the market that provides a vast number of tools with a smooth integration.

I was tempted to go with the spin up your own web service route. Deploying a small kubernetes cluster with a highly available web service responding to all API requests from my app sounds about right to me. However, I also know what it feels to maintain a lot of code. Trust me, you don’t want to write a lot of code. Code is liability and the simpler it is - the better.

Enough about the personal preference rant - let’s get on to what I really wanted to talk about. There are some things I wish I knew earlier rather than spending time refactoring towards the right solution. Here’s my list of items you wish you knew about firebase before diving in.

Secure your data

Firebase is great for not letting you deal with password, encryption and storage of it. You can get this wrong in many ways. So, I would first recommend using this as your primary authentication service. Firebase provides a default user object that stores information like display name, profile photo, phone number and other PII data. You want to separate out data related to a user that you wish to show others as opposed to what is considered personal. Therefore, showing email address of user A to user B is not ethical unless user A has given consent for the same. Separate the information out using another table/database that stores non-pii related data.

Provide anonymous authentication functionality in your app. This is extremely important. By default, when you spin up a database in Firebase - you can provision it in test mode. This let’s unauthorized access to the data for 30 days but you shouldn’t do this. If you want to protect this, make sure you specify this rule in your database allow read, write: if request.auth.uid != null;. This makes it possible for read/write with an authentication id only. This will discipline you to build your authentication workflow in the correct way. When the permissive 30 days ends, shit is going to hit the ceiling and you will commit the sin called “Refactor”. It looks great but isn’t it a rabbit hole ? Or is it an excuse to procrastinate.

The ideal authentication workflow is - you get an anonymous id which is later linked to your prefered way of signing up user. This prefered way can be email and password or simply sign in with Google. Storing uid in your database collection further lets you restrict data reads/write to only user who authored it. This means editing personal user configs can be protected the correct way. You don’t want user A editing name/phone number for user B. You once again specify this in your authentication rules but do it only for the documents that needs the additional security.

Don’t store plain-text password and use UUID wherever possible. You don’t want your users figuring out a piece of data that can be used to co-relate information about other users. This means auto-incrementing ids with unsecure API will make it possible for the worst-case scenarios explained earlier. Firebase provides ease of use but you still need to secure it correctly to make sure there is no fallout.

Choose Firestore to Google cloudstore

I ran into the most unfortunate scenario here. If you previously signed up for Google cloud to get that juicy 100$ credit before provisioning your Firebase database, you’re going to get Google Cloudstore as the storage option. This is not what you want because changing it to Firestore takes a week during which no writes can happen to your database. Google Cloudstore is eventually going to be replaced by Firestore - but it looks like this is the default in a workflow which I ran into. I circumvented the issue by just creating a new project. Who wants to wait a week for changing from an almost empty database to a correct one, when you can start afresh.

Use Codable and syntax sugars for cross-conversion

Once, you setup Firestore and start querying data from your app - you will soon realize you want to be able to convert between Firebase generated documents to your Data objects. Here is the link to the official document where it says to use Codable protocol for your custom objects. I cannot stress how important this is since it makes conversion a breeze. One thing I noticed is that the documentation specifies some syntactic sugars none of which are available in v6.22.0. I did provide Google feedback on that bit of the documentation. If you like to pin your dependencies like me - here is how I did the work-around on getting a similar result as the documentation.

import FirebaseFirestore

extension Encodable {
    func asDictionary() throws -> [String: Any] {
      let data = try JSONEncoder().encode(self)
      guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
        throw NSError()
      }
      return dictionary
    }
}

extension DocumentSnapshot {
    
    func data<T: Decodable>(as: T.Type) throws -> T {
        let jsonData = try JSONSerialization.data(withJSONObject: data() as Any, options: [])
        let object = try JSONDecoder().decode(T.self, from: jsonData)
        return object
    }
}

extension DocumentReference {
    
    func setData<T: Encodable>(from: T, completion: ((Error?) -> Void)? = nil) -> Void {
        let dictData = try? from.asDictionary()
        if dictData == nil {
            completion!(EncodableError.encodingFailed)  /// This is a custom error.
        } else {
            setData(dictData!, completion: completion)
        }
    }
}

This exposes the from and to syntax helper for setting and getting data from Firestore. Also, a note here - I love the completion handler pattern more than the delegate pattern. You can also use the delegate pattern here but I prefer this since I am more familiar with callbacks than a iOS only design pattern. I think the delegate pattern is very unique to iOS and requires a bit of a learning curve before getting used to it.

Final Thoughts

I only have the above points currently but implementing them correctly is a challenge. Especially, securing your data and linking anonymous credential with personal account. I’ve spent a good amount of time getting my authentication workflow right - so I don’t screw up on security. I would encourage to read the docs carefully before refering to any other article on “how to do X using Firebase in Swift”. RTFM before someone else tells you to do it. Also, someone give the technical writer for Firebase documentation a medal! A well-written piece indeed.