Retrieve Default Remote Config Values at Compile Time on iOS

The quest for a more dynamic Remote Config experience

César Vargas Casaseca
Axel Springer Tech
6 min readMar 20, 2020

--

We have been using Firebase Remote Config at WELT for a while now. As it is written in their documentation,

You can use Firebase Remote Config to define parameters in your app and update their values in the cloud, allowing you to modify the appearance and behaviour of your app without distributing an app update.

Especially the last phrase is relevant to us, we do not want to release a new version to the App Store or Google Play every time we want to change a value in our configuration. We use Remote Config manyfold in our News app, e.g. specifying our backend endpoints or dynamically displaying different sections. On top of that, we can run our A/B test experiments through it, having two different variants of a value and determining which one is more effective according to a subject’s response.

It is because of that that we need it to be fast and promptly available, right from the user first app start up. Since we use it for critical features, it is also an essential requirement that the process is robust; if for some reason the fetching of a value fails, we should have default values as fallbacks. That avoids any critical state in the app for lack of a necessary value.

As I mentioned before, we face an instance of this problem right at the first start. At this point, Remote Config values are not fetched yet, therefore having the cache totally empty. Since we need values before even the fetch happens, we will have to consume the statically defined default values.

Once acknowledged the need of defining some default values, we had to find the proper way to implement it. We firstly resorted to the simplest way, to make use of the API method to define in our code these default values, and pass them through an NSDictionary. On Android meanwhile they have to do the same in their code.

The improvement

That solution worked fine, but soon we found out that there was room for optimisation given some evident caveats. Namely:

  • We have duplicated code in both iOS and Android
  • We have two sources of truth, the Remote Config dashboard and our code.
  • This means that we have an extra layer to maintain.

Somehow this is precisely what we were trying to avoid with Firebase Remote Config. What if we could even make that dynamic?

At this moment we thought of Xcode Build Phases, and the Remote Config API. What if we could download the current values of our Remote Config Dashboard as a Build Phase before compiling, and use these values synchronously first thing when we start the app? That way we would forget about the need of maintaining the code in both platforms, and the values will be updated with the latest version at compile time. With that on mind, we started to work.

The Script

The first step is then to create a script to be called at Build time, generating a file to be copied to the project also at Build time. We chose Python given its learning ease, readability and Google support in Firebase Remote Config. And why not say it, it is a cool language!

As specified in the docs, we need an access token to authenticate and authorize API requests:

In the Firebase console, open Settings > Service Accounts.

Click Generate New Private Key, then confirm by clicking Generate Key.

Securely store the JSON file containing the key.

Ideally you add that file to the ignore repo file, but if you still need it to run in your CI you can encrypt it as we will detail later.

Apart from it, we obviously need the calling URL that provides us with our configuration values. It has this format:

https://firebaseremoteconfig.googleapis.com/v1/projects/my-project-id/remoteConfig

Once we have these requirements, we are ready to start writing legit Python code. We want to:

  • Make the script accept two parameters, the path for the private key JSON and the output path.
  • Create credentials with the JSON file containing the key:
  • Create an Authorized session with the credentials, and use it to call the previously determined URL
  • Parse this values extracting the default values and save it into a JSON file, with the path specified as an argument. Please note that we are getting the default values, so if we are performing an A/B testing case it will not gather that data:

Since the Remote Config iOS SDK accepts a PLIST file for defaults values as well, we could at this moment parse and generate that format. However, as this would be used as well on Android, we decided to go for the JSON standard.

The Build Phases

Once we have the script that retrieves the defaults, it is time for the Xcode project to make them available to our code. For that, as we mentioned earlier, we create two build phases. The first one calls the script with the specified parameters:

The sh code is as follows:

Secondly, we need to copy that file into our project. We could have used the already present Copy Bundle Resources phase, but we wanted to make it more explicit and visible:

With that, we can access to the JSON file after building as to any other resource, through our Xcode project navigator:

The Swift Code

We are now there, back from the cryptic and mysterious lands of Python and scripting in Xcode territory, where a Swift iOS developer feels cozy.

Our last duty is therefore to assign these defaults in the Remote Config, to make available and accessible right from the first app start. We have to enforce that this happens before any Remote Config value is accessed:

We parse the JSON to convert it into the expected Dictionary format. As mentioned before, we could use the PLIST method if already created that extension file in our script:

open func setDefaults(fromPlist fileName: String?)

Bonus Track: The CI

Everything until now is delightful, but what happens with our Continuous Integration Process? In our case Travis CI takes care of building and releasing the QA and Production versions for Testflight. If, as we said before, we cannot commit the private key to authorize the script request all the previous effort would have been in vain.

The solution? To add it to the repo encrypted, and let Travis decrypt it before building. That way we keep it secure while mimicking the local build behaviour in our CI.

If you use Travis you can follow their guide to encrypt a file here. Just don’t forget to add python dependencies and the decryption command at .before_install phase in the travis.yml.

If you opted for Jenkins, here there is a good point to start. I am sure every continuous integration & continuous delivery platform include this capability in their toolset.

Bonus Track II: The Android

My colleague Paulina Strychacz extended the script when implementing this feature on Android, so it directly parses the defaults into an XML file. Since the Android Remote Config SDK accepts that format when setting the defaults, they skip the intermediate step of parsing the JSON content:

The Conclusion

With this article we proved how we can make use of different technologies to extend the potential of a known platform as Remote Config. We:

  • Created a Python script to download and parse the default values included in our Remote Config dashboard.
  • Showed how we can call that script in a Xcode Build Phase so we have them accessible from our codebase.
  • Set these defaults in our app Swift codebase through the Remote Config SDK.
  • Encrypted the private Key so it can be used in the CI.

I hope you can use this approach in your own apps, and please do not hesitate to add a comment if you have suggestions or questions.

I would like to thank Paul Hackenberger for this idea, and Paulina Strychacz for the convenient improvements on the Android side.

Happy scripting!

You can access to the script here.

--

--

César Vargas Casaseca
Axel Springer Tech

Senior iOS Developer at Automattic. Allegedly a clean pragmatic one. Yes, sometimes I do backend. Yes, in Kotlin, Javascript or even Swift.