How to Deal with Different Environments in Xcode
All our apps are developed in several different environments. Let’s have a look at what we call an environment, what the motivation to have different environments in one app is and what type of inconveniences it could bring. We’ll also unscover a part of our technological stack which helps us to do it the way we do it. ?
What is an environment?
An environment is a set of settings which changes accordingly tohe user group the app is distributed to. We’ll look closer at the parts of the environment beginning with the most obvious ones and then moving to those that might be specific to our stack.
To make this post more clear we should define the environments we use in most of our apps. We usually use 3 different environments:
- Development - for app development
- Stage - for testing
- Production - well, that’s the production ?
Address of API that app uses
Well, 99 % of our apps (maybe even more) need to communicate with some kind of backend. This is the first case when environments are extremely useful. In Development and Stage environment we need to be able to safely simulate any behavior that should be implemented (or tested) in the app. That’s the reason why the address of API is always part of our environment.
Tokens and keys
This part of the environment is related to the previous one - if you use different APIs it is very likely that you’ll have to use different keys to the services which are used on both server and client sides (e.g. Google services, image hosting etc.).
More detailed errors
It would be nice if no errors have ever occured, wouldn’t it? Unluckily that’s still not possible so we have to deal with presenting errors to the user. Well, this can become a bit tricky. In Production the user is generally not interested to know which HTTP code was returned by the server or why did the mapping response fail. But in Stage and Development this information is very useful so that’s the reason why our environment contains a button turning on and off flag whether the detailed errors should be displayed or not.
Bundle identifier and app name
You might wonder why bundle identifier? It is quite simple - we want to have all the environments installed side by side. We also need to distinguish between all those different app versions so we also change the name based on the environment. Stage environment also has its build number on the app icon.
Configuration files for Firebase
All our apps use Firebase at least for crash reporting and analytics. Because different environments use different bundle identifiers, to communicate with different API they also need to communicate with different Firebase. This means that we need to tie selected API, bundle identifier and Firebase config files.
How do we switch environments?
Let’s introduce you to our Xcode setup - our schemes copy the environments. We also use 3 configurations - Debug, AdHoc and AppStore. While running app from Xcode we use Debug, when archiving AdHoc and AppStore is used for production builds.
At first we’ll introduce you to our folder structure - each environment has its own folder. Here we have build phase which handles selecting the correct folder
In the script there are a few details that might need an explanation. In target’s project settings we have a custom variable ACK_ENVIRONMENT_DIR where we store all the environmental folders. The variable CURRENT_ENV_FILE is a location of where we write the current scheme - the problem is that in Xcode it is not possible to determine which scheme is being built. We have solved this issue by adding a pre-build script to each scheme which writes the name of the scheme into this file.
One of the files included in the environment folder is environment.plist. This is where we store all of our environment specific settings. From this file we use our custom script which later generates enum with all values from plist.This script is a part of a building process so it is rewritten with every rebuild. We are about to remove our custom script and replace it with Swiftgen as we use it for images and localization strings. Generated file is then compiled with the rest of the app so it is all safe.
Another part of the environmental folder are Firebase configuration plists for all build configurations. We switch bundle identifiers according to a build configuration. So we need to have plists for all Firebase apps and projects we want to use. This generally means that we need to have 9 plists (3 environments * 3 bundle identifiers). We then use a build phase that selects correct plist according to current environment and build configuration.
How to get environment values in code?
As mentioned above we have a script that generates Swift code so it is as simple as getting a value from a static variable from an enum. Here it might be a good point to say how we define the values, which are stored in those environment plists. It would be easy to define a single string property with environment name and then checking it in a code like this.
switch Environment.name {
case "Stage": baseURL = "https://jsonplaceholder-stage.typicode.com"
case "Development": baseURL = "https://jsonplaceholder-dev.typicode.com"
case "Production": baseURL = "https://jsonplaceholder.typicode.com"
default: // and what now?
}
This is not the way we do it. This would mean that adding or removing an environment would require changing the code. This is not the approach we like. That is why we never check what environment the app runs in. This is how our typical environment file looks like:
enum Environment {
enum Google {
static let mapsKey = ""
}
enum API {
static let baseURL = URL(string: "")!
}
enum Resizin {
static let clientKey = ""
static let project = ""
}
}
And then also the code looks much nicer, right? ?
let baseURL = Environment.API.baseURL
Well this is how we handle different environments, if you would like to ask anything, just ping us on Twitter or see our GitHub. Honestly, you can see our environment switch in action in our iOS-MVVM-ProjectTemplate which was open sourced within the grant from Ethereum Foundation.