Firebase Remote Config Tutorial for iOS
In this tutorial, you’ll learn how to make changes to your iOS app immediately without resubmitting to the App Store.
Version
- Swift 5, iOS 14, Xcode 12

Remember that time you published your app, and it was perfect in every way? You never had to touch another line of code because you managed to get everything just right the first time around?
Me neither.
Being a successful app developer usually means making frequent changes to your app. Sometimes, these changes are new features or bug fixes. But sometimes, the most impactful updates are one-line changes to your code, like adjusting a line of text or nerfing a powerful unit in a tower defense game.
Although these kinds of changes are easy to make, publishing them is still a multi-day process. Wouldn’t it be nice if you could make some of these tweaks without having to go through that whole process?
Firebase Remote Config gives you that power. Throughout this tutorial, you’ll use the PlanetTour sample app to learn how to change text, colors, and other behavior without having to publish new builds! You’ll be able to avoid going through the App Store approval process and deliver small changes to your users instantly. Once you’ve mastered simple tweaks, you’ll learn the more powerful feature of delivering different sets of content to different users.
Getting Started
Get started by downloading the materials via the Download Materials link at the top or bottom of this tutorial. Unzip and run the starter project app. Swipe to view different planets and tap each to get some (mostly accurate) extra information.
The app you’ve just downloaded is made by PlanetTour Apps, Inc., where things are going great until, one day, Greg from marketing decides PlanetTour should switch to a green color scheme in celebration of Earth Day.
It’s an easy enough fix — if you look in AppConstants.swift, there’s an appPrimaryColor
property you can change, which will affect many of your text label colors. Pushing this change out to your users would involve publishing a new build, submitting it to the App Store, getting it approved, and then hoping all your users download it before Earth Day. You’d have to do the whole process again to revert the change once Earth Day was over.
Wouldn’t it be nice if you could just alter these values … from the cloud?
Installing the Remote Config Library
To get started using Remote Config instead of the hard-coded values currently in AppConstants
, you need to create a project in the Firebase Console, associate it with the PlanetTour app, and then install the Firebase Remote Config library.
Creating a Project in Firebase Console
The first step is to create a project. To do this:
- Open firebase.google.com/console
- Click Add project.
- Name your project PlanetTour, then click Continue:
- Next, be sure that Enable Google Analytics for this project is selected, then click Continue:
- Next, select an existing Google Analytics account or create one, then click Create project:
You have created your project! Now, you must associate it with the PlanetTour app.
Associating the Project with the PlanetTour App
After creating your project, you’ll be redirected to your app’s homepage. To connect the project with the app:
- Add an iOS app by clicking the iOS logo:
- Add the bundle ID of your project — com.raywenderlich.PlanetTour — and the app nickname — PlanetTour — but leave the App Store ID field blank, and then click Register App:
- Click the Download button to download a GoogleServices-info.plist file:
- At this point, your browser will download a GoogleServices-info.plist file for you. Drag this file into your Xcode project, as shown in the screenshot above from the Firebase instructions. Be sure to select Copy Items if Needed.
- Click Next through the remaining few steps in the setup wizard. Don’t worry: You’ll walk through those instructions next. Finish the wizard by clicking with Continue to Console:
- Switch back to Xcode and close the PlanetTour project.
You’re almost there now! But you still have to install the Remote Config Library.
Installing the Firebase Remote Config Library
You need to enable your app to find new values on the internet. To do this:
- Open a Terminal window and navigate to your project. The easiest way to do this is to type “cd ” (with a space at the end) and wait to submit the command. Then open a Finder window and locate the folder containing your Xcode project. Drag and drop the folder from Finder into Terminal. The location of the folder will be filled into the command in Terminal. Press Return on your keyboard in Terminal to submit the command.
- Type
pod init
and press Return to create a basic Podfile in the project folder. - Open the Podfile using your favorite text editor, and replace its contents with the following, then save it:
target 'PlanetTour' do # Comment this line if you're not using Swift # and don't want to use dynamic frameworks use_frameworks! platform :ios, '12.0' # Pods for PlanetTour pod 'Firebase/Core', '~> 7.0.0' pod 'Firebase/RemoteConfig', '~> 7.0.0' # Remove Xcode 12 warnings post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' end end end end
- Run pod install in Terminal.
- Open the new PlanetTour.xcworkspace with Xcode. (Note: You want the workspace, not the project.) The workspace incorporates the code from CocoaPods specified in your Podfile.
- Use the Project navigator to open AppDelegate.swift. Add the following below
import UIKit
:import Firebase
Next, add the following implementation to
application(_:didFinishLaunchingWithOptions:)
before thereturn
statement:FirebaseApp.configure()
This method reviews the libraries you already have installed and initializes them, using the constants provided to your project when you added the GoogleServices-info.plist file. The Remote Config library now knows where to look on the internet to find new values.
xed .
Build and run your app, again. The app should look as it did previously except that you’ll see debug information in your console output that you didn’t see before.
Congratulations! You’ve installed Remote Config! Now, you can use it in the rest of this tutorial.
How Remote Config Works
Oversimplified, Remote Config works similarly to a [String: Any?]
dictionary living in the cloud. When your app starts, it grabs any new values it might need from the cloud, then applies them on top of any old values you might have specified as defaults.
The general process for using Remote Config looks like this:
- Provide Remote Config with defaults for any value you might change in the future.
- Fetch any new values from the cloud. You can find these stored in a cached holding pattern on your device.
- “Activate” those fetched values. When this happens, it applies those fetched values on top of your existing default values.
- Query Remote Config for values. Remote Config will give you either a value from the cloud, if it found one, or a default value based on the provided key.
One important thing to note is that these new values you fetch are generally a subset of the default values that you supply. You can take nearly any hard-coded string, number or Boolean in your app and wire it up to use Remote Config. This gives you the flexibility to change many aspects of your app later while still keeping your actual network calls nice and small.
Enough theory. Time to put this into practice!
Using Remote Config
First, select the Utilities folder in the Project navigator in Xcode. Right-click to create a new file. Select Swift file. Name it RCValues.swift and create it in the default folder suggested by Xcode.
Add the following to the end of the file:
import Firebase
class RCValues {
static let sharedInstance = RCValues()
private init() {
loadDefaultValues()
}
func loadDefaultValues() {
let appDefaults: [String: Any?] = [
"appPrimaryColor": "#FBB03B"
]
RemoteConfig.remoteConfig().setDefaults(appDefaults as? [String: NSObject])
}
}
Here, you use the Singleton pattern for RCValues
. You’ll be able to access the values from the sharedInstance
anywhere in your project. Inside loadDefaultValues()
, you’re passing along a set of keys and values to Remote Config as defaults. Right now, you’re supplying only one value, but don’t worry, you’ll add more later.
Fetching Values From the Cloud
Next, you need to ask Remote Config to fetch new values from the cloud. Add the following method below loadDefaultValues()
, just before the closing curly brace of the class:
func activateDebugMode() {
let settings = RemoteConfigSettings()
// WARNING: Don't actually do this in production!
settings.minimumFetchInterval = 0
RemoteConfig.remoteConfig().configSettings = settings
}
By default, Remote Config will cache any values it retrieves from the cloud for about 12 hours. The client-side throttle ensures that you don’t ping the service too frequently. In a production app, this is probably fine. But when you’re doing development — or following a Firebase Remote Config tutorial online — this can make it really tough to test new values. So instead, you’re specifying a minimumFetchInterval
of 0
to ensure you never use the cached data.
Add the following method to fetch these values:
func fetchCloudValues() {
// 1
activateDebugMode()
// 2
RemoteConfig.remoteConfig().fetch { [weak self] _, error in
if let error = error {
print("Uh-oh. Got an error fetching remote values \(error)")
// In a real app, you would probably want to call the loading
// done callback anyway, and just proceed with the default values.
// I won't do that here, so we can call attention
// to the fact that Remote Config isn't loading.
return
}
// 3
RemoteConfig.remoteConfig().activate { _, _ in
print("Retrieved values from the cloud!")
}
}
}
Here’s what’s happening in that code:
- By enabling debug mode, you’re telling Remote Config to bypass the client-side throttle. For development purposes or testing with your 10–person team, this is fine. But if you launch this app to the public with your millions of adoring fans, you’re going to hit the server-side throttle pretty quickly, and Remote Config will stop working. This is the whole reason you have a client-side throttle in the first place. Before you launch this app for real, make sure you disable debug mode and set your
minimumFetchInterval
to something a little more reasonable, like 43200 — that’s 12 hours to you and me. - If Remote Config encounters an error during the fetch process, it will exit before applying the fetched values. Don’t worry about the warning for
weak self
. You’ll add code to address the warning later in this tutorial. - If the values are downloaded successfully from the cloud, then Remote Config will use the new values instead of the default ones hard-coded in the app.
Add the following to the end of init()
to call your new method:
fetchCloudValues()
Running Your Code
Open AppDelegate.swift and add the following to application(_:didFinishLaunchingWithOptions:)
, below FirebaseApp.configure()
:
_ = RCValues.sharedInstance
The underscore character means you’re not planning to use the name of the constant. Simply by accessing the value of sharedInstance
, you initialize it and populate its default values.
Build and run your app, and you should see the following line in your debug console:
Retrieved values from the cloud!
Using Remote Config Values
Now that you’re downloading these values, try printing them to the console. Open RCValues.swift, then add the following to fetchCloudValues()
, right after the “Retrieved values from the cloud” line:
print("""
Our app's primary color is \
\(RemoteConfig.remoteConfig().configValue(forKey: "appPrimaryColor"))
""")
The above code will grab the appropriate value for your appPrimaryColor
key.
Build and run your app. You should see a line like this:
Our app's primary color is <FIRRemoteConfigValue: 0x61000003ece0>
Well, that’s somewhat helpful, but you’re kind of hoping for a string value.
Remote Config retrieves values as RemoteConfigValue
objects. You can think of these as wrappers around the underlying data, which is represented internally as a UTF8-encoded string. You’ll almost never use this object directly. Instead, you’ll call a helper method like numberValue
or boolValue
to retrieve the actual value you want.
Replace the line you just added with:
let appPrimaryColorString = RemoteConfig.remoteConfig()
.configValue(forKey: "appPrimaryColor")
.stringValue ?? "undefined"
print("Our app's primary color is \(appPrimaryColorString)")
Build and run your app. This time you’ll see:
Our app's primary color is #FBB03B
That’s more like it. Remote Config provides you the default value of a hex color code that you supplied earlier.
Updating Values From the Cloud
Now that you’re getting proper values from Remote Config, try supplying new values from the cloud.
Open the Firebase Console. Look in the left sidebar and expand the Engage section. Click the Remote Config option:
Click Add a parameter. In the form, enter appPrimaryColor for the key and Greg from Marketing’s favorite new green — #36C278 — for the value.
Click Add Parameter, then click Publish Changes twice to update the changes.
Build and run your app.
See what’s in the console now:
Our app's primary color is #36C278
Hooray! You’re updating values from the cloud!
Changing Your App’s Look and Feel
Now, it’s time to hook up your app to use this new value.
First, add an enum
to represent your keys. Using raw strings for key names is a recipe for disaster — or at least you’ll spend an afternoon hunting down a mystery bug because you mistyped a key name. By using an enum
, Swift can catch errors at compile-time instead of runtime.
Open RCValues.swift and add the following above the class definition:
enum ValueKey: String {
case appPrimaryColor
}
Next, update loadDefaultValues()
to use this enum instead of the raw string:
let appDefaults: [String: Any?] = [
ValueKey.appPrimaryColor.rawValue : "#FBB03B"
]
Next, add the following helper method to RCValues
, which takes in a ValueKey
and returns a UIColor
based on the string from Remote Config:
func color(forKey key: ValueKey) -> UIColor {
let colorAsHexString = RemoteConfig.remoteConfig()[key.rawValue]
.stringValue ?? "#FFFFFF"
let convertedColor = UIColor(colorAsHexString)
return convertedColor
}
Finally, change the places in your app using the old AppConstants
value to use this new RCValues
helper method instead.
Do this in three locations:
1. Open ContainerViewController.swift and change the following inside updateBanner()
:
bannerView.backgroundColor = AppConstants.appPrimaryColor
to this:
bannerView.backgroundColor = RCValues.sharedInstance
.color(forKey: .appPrimaryColor)
2. Open GetNewsletterViewController.swift and change the following inside updateSubmitButton()
:
submitButton.backgroundColor = AppConstants.appPrimaryColor
to this:
submitButton.backgroundColor = RCValues.sharedInstance
.color(forKey: .appPrimaryColor)
3. Open PlanetDetailViewController.swift and change the following inside updateLabelColors()
:
nextLabel.textColor = AppConstants.appPrimaryColor
to this:
nextLabel.textColor = RCValues.sharedInstance.color(forKey: .appPrimaryColor)
To be thorough, open AppConstants.swift and delete the following:
static let appPrimaryColor = UIColor(rgba: "#FBB03B")
See ya later, hard-coded value …
Now, build and run your app. You should see the new green throughout the app:
Addressing the Timing of New Values
You don’t have a lot of control over when these new values get applied. The first time you ran the app, you probably saw the default orange on the main menu but then the new green on the planet detail screens once your new values were loaded from the cloud.
This can confuse your users. In this case, you’re only changing some label colors, but it can be quite confusing if your app changes texts or values affecting its behavior while your user is running it.
You can deal with this issue in many ways, but perhaps the easiest might be to create a loading screen. In this tutorial, there’s already one partially set up for you.
Hooking Up a Loading Screen
First, make the loading screen the initial view controller of your app. Open Main.storyboard and Control-drag from your Navigation Controller to the Waiting View Controller — it’s the view controller with the black background, although it might be easier to do this Control-dragging in your storyboard outline. Select root view controller from the pop-up to make your loading screen the initial screen when your app loads.
Now, add the logic to transition to the main menu when Remote Config finishes loading.
Open RCValues.swift, add the following below the sharedInstance
property:
var loadingDoneCallback: (() -> Void)?
var fetchComplete = false
Next, find fetchCloudValues()
and add the following after the line that prints the app’s primary color:
self?.fetchComplete = true
DispatchQueue.main.async {
self?.loadingDoneCallback?()
}
Here, you set fetchComplete
to true
, indicating fetching is complete. Finally, you call the optional callback to inform the listener that the Remote Config values have finished loading. You can use this to tell a loading screen to dismiss itself.
Open WaitingViewController.swift and add the following method:
func startAppForReal() {
performSegue(withIdentifier: "loadingDoneSegue", sender: self)
}
Next, replace viewDidLoad()
with the following:
override func viewDidLoad() {
super.viewDidLoad()
if RCValues.sharedInstance.fetchComplete {
startAppForReal()
}
RCValues.sharedInstance.loadingDoneCallback = startAppForReal
}
Here, you’re making startAppForReal()
the method RCValues
calls when all its values finish loading. You’re also adding a check just in case RCValues
somehow manages to finish its network call before the waiting screen finishes loading. This should never happen, but it never hurts to code defensively!
Build and run. You’ll see the waiting screen appear for a short while, depending on your network speed, before jumping into the rest of your app. If you change the value of your app’s primary color in the Firebase console and restart your app, the new color will properly appear everywhere in your app. Remember to click Publish Changes in the Firebase console.
Hook Up the Rest of Your App
Now that you’ve converted one value from AppConstants
to RCValues
, you can now convert the rest! In this section, you’ll see behind the scenes how the app is wired up. You’ll also see how to organize the supporting code for Remote Config, which you can apply in the future to an app of your own.
Open RCValues.swift and replace ValueKey
with the following:
enum ValueKey: String {
case bigLabelColor
case appPrimaryColor
case navBarBackground
case navTintColor
case detailTitleColor
case detailInfoColor
case subscribeBannerText
case subscribeBannerButton
case subscribeVCText
case subscribeVCButton
case shouldWeIncludePluto
case experimentGroup
case planetImageScaleFactor
}
Next, replace loadDefaultValues()
with the following:
func loadDefaultValues() {
let appDefaults: [String: Any?] = [
ValueKey.bigLabelColor.rawValue: "#FFFFFF66",
ValueKey.appPrimaryColor.rawValue: "#FBB03B",
ValueKey.navBarBackground.rawValue: "#535E66",
ValueKey.navTintColor.rawValue: "#FBB03B",
ValueKey.detailTitleColor.rawValue: "#FFFFFF",
ValueKey.detailInfoColor.rawValue: "#CCCCCC",
ValueKey.subscribeBannerText.rawValue: "Like PlanetTour?",
ValueKey.subscribeBannerButton.rawValue: "Get our newsletter!",
ValueKey.subscribeVCText
.rawValue: "Want more astronomy facts? Sign up for our newsletter!",
ValueKey.subscribeVCButton.rawValue: "Subscribe",
ValueKey.shouldWeIncludePluto.rawValue: false,
ValueKey.experimentGroup.rawValue: "default",
ValueKey.planetImageScaleFactor.rawValue: 0.33
]
RemoteConfig.remoteConfig().setDefaults(appDefaults as? [String: NSObject])
}
Next, add three helper methods at the end, below color(forKey:)
, to allow retrieving values other than colors:
func bool(forKey key: ValueKey) -> Bool {
RemoteConfig.remoteConfig()[key.rawValue].boolValue
}
func string(forKey key: ValueKey) -> String {
RemoteConfig.remoteConfig()[key.rawValue].stringValue ?? ""
}
func double(forKey key: ValueKey) -> Double {
RemoteConfig.remoteConfig()[key.rawValue].numberValue.doubleValue
}
Next, replace every part of your app that uses AppConstants
with the corresponding call to RCValues
.
You’ll make nine changes throughout your app:
1. Open ContainerViewController.swift and replace updateNavigationColors()
with the following:
func updateNavigationColors() {
navigationController?.navigationBar.tintColor = RCValues.sharedInstance
.color(forKey: .navTintColor)
}
2. Replace updateBanner()
with the following:
func updateBanner() {
bannerView.backgroundColor = RCValues.sharedInstance
.color(forKey: .appPrimaryColor)
bannerLabel.text = RCValues.sharedInstance
.string(forKey: .subscribeBannerText)
getNewsletterButton.setTitle(RCValues.sharedInstance
.string(forKey: .subscribeBannerButton), for: .normal)
}
3. Open GetNewsletterViewController.swift and replace updateText()
with the following:
func updateText() {
instructionLabel.text = RCValues.sharedInstance
.string(forKey: .subscribeVCText)
submitButton.setTitle(RCValues.sharedInstance
.string(forKey: .subscribeVCButton), for: .normal)
}
4. Open PlanetDetailViewController.swift and in updateLabelColors()
, replace the line:
nextLabel.textColor = AppConstants.detailInfoColor
with:
nextLabel.textColor = RCValues.sharedInstance.color(forKey: .detailInfoColor)
5. Replace the line:
planetNameLabel.textColor = AppConstants.detailTitleColor
with:
planetNameLabel.textColor = RCValues.sharedInstance
.color(forKey: .detailTitleColor)
6. Open PlanetsCollectionViewController.swift, and inside customizeNavigationBar()
, replace the line:
navBar.barTintColor = AppConstants.navBarBackground
with:
navBar.barTintColor = RCValues.sharedInstance
.color(forKey: .navBarBackground)
7. Inside collectionView(_:cellForItemAt:)
, replace the line:
cell.nameLabel.textColor = AppConstants.bigLabelColor
with:
cell.nameLabel.textColor = RCValues.sharedInstance
.color(forKey: .bigLabelColor)
8. Open SolarSystem.swift and, inside init()
, replace the line:
if AppConstants.shouldWeIncludePluto {
with:
if RCValues.sharedInstance.bool(forKey: .shouldWeIncludePluto) {
9. Finally, inside calculatePlanetScales()
, replace the line:
scaleFactors.append(pow(ratio, AppConstants.planetImageScaleFactor))
with:
scaleFactors.append(pow(
ratio,
RCValues.sharedInstance.double(forKey: .planetImageScaleFactor)))
Whew! That was a lot of changes, but now you should have your entire app switched over. To complete the refactoring, do a search in your app for AppConstants — you should have only one result left, which defines the struct itself:
To be really sure, use the Project navigator to locate your AppConstants.swift file, select it and delete it. Build and run. If your refactoring was successful, you won’t see any errors.
Now that your app is fully wired up to Remote Config, you can make other changes besides the green color that Greg likes so much.
Adding Further Changes to Your App
Open the Firebase Console. Make sure you’re in the Remote Config section, then click Add Parameter. Type navBarBackground for the key and #35AEB1 for the new value, then click Add Parameter. Then do the same thing to set navTintColor to #FFFFFF. Click Publish Changes and confirm in the modal box to publish these changes to your app.
When you’re finished, your console should look like this:
Switch back to Xcode, then build and run.
Tap a planet, and your app should look like this:
Feel free to play around! Change some other values. Mess around with the text. See what kind of stylish — or gaudy — color combinations you can come up with.
But when you’re finished, come back to this tutorial because you have an international crisis to deal with!
Bringing Back Pluto
Things are rotten in the state of Denmark! Although most of the world has begrudgingly accepted that Pluto isn’t a planet, the Scandinavian Society for Preserving Pluto, a totally-not-made-up society of rabid Pluto fans, has lobbied hard for Pluto to be a planet and, hence, worthy of inclusion in the PlanetTour app. Protests are mounting in the streets of Copenhagen! What can you do?
This seems like a simple job for Remote Config! You could set shouldWeIncludePluto
to true
. But hang on — that will change this setting for all of your users, not just those in Scandinavia. How can you deliver different settings only to people that speak different languages?
Conditions to the Rescue!
Remote Config’s ability to deliver different sets of data to different users is what makes it more sophisticated than just a simple dictionary in the cloud. Take advantage of this feature to make Pluto a planet again for your Scandinavian users.
First, open the Firebase Console, make sure you’re in the Remote Config panel and click Add Parameter to add a new parameter.
Enter shouldWeIncludePluto as the parameter key.
Next, click the drop-down next to the value field labeled Add value for condition.
Next, select Define New Condition:
In the dialog, give the new condition a name of Pluto Fans.
In the drop-down underneath, select Languages.
From the languages list, select Danish, Finnish, Icelandic, Norwegian and Swedish:
Click Create Condition.
Next, add a value of true in the Value for Pluto Fans field, and a value of false as your Default value:
Finally, click Add Parameter and then click Publish Changes to push these changes to the world.
Build and run to see the results.
If you don’t speak one of these northern languages, you won’t see Pluto in your list of planets. If you want to test the experience for your Scandinavian users, I recommend booking a flight to Copenhagen, getting yourself a new Danish iPhone, and then running your app on it — perhaps while enjoying a smoked salmon, open-faced sandwich.
A slightly more thrifty option (potentially involving less jet lag) is to open the Settings app on your device or simulator. Select General > Language & Region > iPhone Language > Dansk — or whatever your favorite Scandinavian language is:
Build and run your app. This time, you should see Pluto back where it belongs, among the other planets. The international crisis averted!
Where to Go From Here?
You can download the completed project for this tutorial using the Download Materials link at the top or bottom of this tutorial. However, please note that you’ll need to create a project in the Firebase Console and drag in your GoogleServices-info.plist for the project to work.
There are more features you haven’t touched on in this tutorial, too. For example, by delivering values to random groups of users, you can run A/B tests or gradually roll out new features. You can also deliver different data sets to specific groups of people you’ve identified in Firebase Analytics, which gives you some nice customization features. In this tutorial, you used the Language setting on the phone to group users, but you could also group users geographically using their IP address to determine the country they live in. Check out the documentation or the next tutorial on this topic!
Now that you have the foundations, there’s a lot more you can do with Remote Config. If you’re developing any kind of game, it can be a great way to tweak your gameplay if your players are finding it too easy or too hard. It’s also an easy way to create a “Message of the Day” kind of feature. Or you might just use it to experiment with a different button or label text to see what your users react to best. Try it out in your favorite app and see what you can change on the fly!
As always, if you have any questions or comments about this tutorial, please join the forum discussion below!
Comments