Firebase is a suite of mobile cloud tools for iOS, Android and Web from Google. Amount these tools is crashlytics, a simple crash reporting tool. To start recording errors with Firebase crashlytics we can create a firebase project per deployment environment. Let’s assume we have three app environments, develop, staging, and production. Each app environment will have a unique app/bundle id to install all variances on a single test device.
The Multiple Firebase Environments Problem
Because each Firebase project has unique keys for each environment, we have multiple configurations that we need to ship with each app variants. On Android, this is easy as the Firebase
google-services.json will contain all variance, and Gradle will pick out the one specific to the App ID we are compiling.
On iOS, on the other hand, we have a single
GoogleService-Info.plist per variant. So, we need a way to pick the right plist per variant.
A common method I’ve seen to address the variance problem is to create an Xcode Target per environment. We then copy the variant plist into each targets source folder and add the Build Phase task to each Target with the input plsit file. While the multi-target method works, it does with the cost of adding a Target purply for a config file, we then need to duplicate the Build Phase task for each Target. The one upside I’ve seen is that it’s quick swapping Targets in Xcode to select a variant; I find the duplications costs more than it should
We parametrize the build pipeline using environment variables. The best way I’ve found so far to inject the environment variables is to either pass them in a compile-time or defined them in the bash profile, whichever works best for your setup.
To make the build parametrize manageably, we need to set up a script that will export environment variables for the build to use.
So, we start by creating a shell file at the root of your project
Configs/env-import.sh. The shell file is not strictly required as you could define the variables to your shell config if you only have a single project. When working in a team or on multiple projects having a shell file works well as a central documentation/edit point
Next, we want to add a
Pre-Actions to our build to invoke our
Pre-Actions/firebase-config.sh that we will define after this. I recommend creating a shell file here over just editing the build schema as it makes Git diffing and shell linting ( Shellcheck) easier.
Now we can define the
Pre-Actions/firebase-config.sh we could copy the plist into the project. Here I've opted to use
PlistBuddy to update a git ignored
GoogleService-Info.plist in my source root; you can also copy the plist file if you prefer;
firebase-config.sh will copy the keys that differ between the info files using PlistBudy
Next, we want to download all the GoogleService-Info.plist from Firebase and put them into Config/firebase
And that’s it. The
firebase-config.sh will update the keys we need for the environment defined in our system or build scripts. When we don't specify any argument, we default to the dev environment.
Firebase Build Phase Scripts
If you have not already done so, let’s add the Build Phase script to initialize Firebase as per the Crashlytics docs
Add Build Phase Firebase script
If you’re using cocoapods like me, update your Build Phase script to use the App and Google info plist as Input Files; This also applies to the dSYM upload.
Adding the Input Files should allow Xcode to skip the script step if input the files have not changed and Xcode can re-use the last build run results.
Symbol uploading can also take advantage of the parameterized builds by adding the $(SRCROOT)/GoogleService-Info.plist as an Input file.
# Input Files
firebase-upload.sh will upload all the symbols it can find. Uploading all the Symbols is possible overkill, and this script could use some fine-tuning. It’s a good sledgehammer approach to uploading ALL symbols 😁.
By moving build parameters outside of Xcode and into shell scripts, we can pass build variants into our build pipeline via system or script define environment variables. External scripts also make it easier to see the diff when updating the pre-build and build steps.
I’m personally not an iOS expert. So, if there is any way to improve this please comment with how you would improve the workflow or suggest alternative ways to handle these kinds of variance