As a developer, you know how exciting it is to launch a new feature in your app. You’ve spent weeks or months refining an idea, developing and testing it, and you’re eager to unveil new functionality to your users, excited to glean their reactions and feedback.
We at Firebase go through this process, too. Today, we are excited to share that you can now receive Remote Config updates in real-time using the latest Remote Config SDKs for Android, Apple platforms, C++, Unity, and Flutter. This means you can quickly update any Remote Config parameter as soon as it’s published on the server! To get started, watch the I/O presentation or continue reading.
For those of you who need a refresher on Remote Config, you use Remote Config to dynamically change and optimize the behavior and appearance of your app without requiring users to download a new version. Remote Config gives you the ability to add feature flags, machine-learning powered personalization, and A/B tests to your app.
Releasing new functionality, while exciting, can also be a little scary…especially when you find yourself pondering those big questions, like:
- When and how should I release my code?
- How do I gather feedback and discover bugs quickly?
- What do I do if something goes wrong? What if there are bugs or crashes?
We all ponder these questions, and there’s one solution that can help answer all of them: using feature flags with real-time Remote Config!
The rest of this blog post will describe the benefits of feature flags and how to use them with real-time Remote Config to increase the velocity of your feature releases with confidence.
What’s a feature flag?
Feature flags are a critical piece of the app development toolkit, letting you use a server-side parameter to toggle a feature on or off on-demand. With feature flags, you can:
- Build new functionality behind the scenes and expose in-progress features to your development team while hiding them from users.
- Stage and progressively roll out features to a subset of users, all without releasing a new version of your app.
- Monitor app performance and gather user feedback during the rollout.
- Quickly roll back changes if the outcome isn’t what you expect.
- Iterate on improvements to re-release.
For example, you might use a feature flag to hide a new user sign up flow while you’re developing it. Or, you are working on a big refactor of your backend and want to use a feature flag to transition between API endpoints.
Feature flags: an app development best practice
We recommend implementing and using feature flags as an app development best practice for a few reasons:
- Bugs in production are a reality no matter how much you test in pre-production environments. Feature flags let you gradually roll out new functionality so you catch those bugs before they affect most of your users. If you discover issues during your rollout – for example, higher crash rates – you can quickly ramp down the feature exposure to zero, with no additional app release required.
- Gathering early feedback is critical to a successful launch. Use feature flags to release to a subset of users, gather feedback, and iterate. Rinse and repeat until you have a feature you’re sure will delight all of your users. And, if there are particular audiences you want to roll out to first – like internal testers, a particular country, or a specific Google Analytics audience – you can use powerful feature targeting tools like Remote Config to select these segments.
- Feature flags let your development team increase velocity because you can build and test features incrementally behind the scenes, shipping many small changes that build up to one much more all-encompassing feature.
Feature flags and branching
Let’s take a deeper look at how feature flagging makes app development more efficient. In a development cycle without feature flags, feature releases are tied to app-version releases. There’s no way to hide a feature once it’s in a released version, so you create a “feature branch” in your codebase and keep it open for as long as feature development takes, which can be weeks, months, or sometimes even longer! When the feature is finally complete, you merge in the feature branch and release your app. The feature itself is finally launched when the user downloads the new version.
This feature branch model of development seems intuitive when you’re releasing new features in new app versions, but it has plenty of downsides, particularly on a multi-person development team. For example, feature branches:
- Require constant maintenance to keep up-to-date.
- Decrease collaboration and shared infrastructure because they encourage isolated feature work.
- Create larger changes that are harder to review and merge, especially when multiple features are landing in a single release.
Not to mention the lack of control you have over user adoption: unless you force app updates (which, let’s face it, isn’t the best user experience), you need to wait for users to upgrade their app before they can access your awesome new feature.
On the other hand, in a development cycle with feature flagged releases, feature code can be merged and even released in an app version without ever being exposed to users. You hide the new work behind a feature flag and launch the feature by enabling the flag, independent from any app version.
In this development model, long-lived feature branches are replaced by small incremental changes that are quickly merged back into the releasable branch. The short-lived nature of branches means they’re never out-of-date. Collaboration is encouraged because the team is working from a common codebase. Changes are easy to review and usually trivial to merge.
Moreover, when multiple features are developed concurrently (as is often the case!), they can be tested against each other along the way so there are no surprises on release day.
Feature flags and Firebase Remote Config
To implement feature flagging, you can use Remote Config, which is a cloud service that lets you configure your apps without releasing a new version. In this case, you can use a Remote Config parameter value as a feature flag, toggling it on or off to dynamically update your app’s behavior and appearance.
Here’s how it works: You create key-value pairs called parameters where you set values on the server side and have in-app defaults to fall back to in case of network connectivity issues. You can then configure values differently for each user or client using conditions. You can use Remote Config conditions to customize parameter values based on client attributes, for example, you could:
- Use a Platform condition to apply a value that tells your app to display a green Android icon.
- Use a New user condition to display a special welcome message only for your newest users.
- Use Languages or Country/Region conditions to target a specific language or country to provide custom currency or a different onboarding flow.
With Remote Config parameters and conditions, the possibilities are endless!
When you implement the Remote Config SDK in your app, your app can retrieve values from the Remote Config server, which will override the in-app default (or default value you set in your code) to change functionality using the conditions, parameters, and values you configured. If you use previous versions of the Remote Config SDK, you refresh these values occasionally - typically when the app was launched or after a set amount of time.
But here comes the fun part! With the latest Remote Config SDKs, you can configure your app to receive real-time updates as soon as they’re published.
With real-time updates for Remote Config, active app instances can obtain new values in real-time and your users will always have your latest update as soon as you publish parameter keys, values, and conditions on the server, making feature flagging more powerful than ever.
And, combining the power of real-time Remote Config updates with app stability insights from Crashlytics and feedback on key metrics from Google Analytics, you’re able to launch features quickly, safely, and confidently.
Configure a feature flag with real-time Remote Config updates
Now that we understand the concepts, let’s walk through an example of how you can use real-time updates to launch a new feature in an app!
Let’s say you have an iOS app called “FireRecipe.” While users love the variety of recipes FireRecipe provides, your team has received a lot of feedback that the recipe times aren’t accurate for all skill levels. You and the team want to launch a more intuitive recipe difficulty feature to help everyone from beginners to pros pick what to cook.
While developing the recipe difficulty feature, you don’t want users to see your in-development work, as it’ll probably confuse them and disrupt their app experience. Plus, there are still bugs you need to fix! So, let’s put the feature behind a feature flag.
To add this feature flag, open the Firebase console and navigate to Remote Config. Create a new parameter called enable_recipe_difficulty
, give it a description, and enter a default value of false
. Then, save and publish.
Add the feature flag to your app code
Now that the flag is set up on the Remote Config server, let’s take a look at how to use that flag in your app code. Let’s assume that you’ve already set up Firebase in your app (if you haven’t, check out Get started with Firebase Remote Config). You can then use the Remote Config iOS SDK to fetchAndActivate
the values from the Remote Config server.
fetch
retrieves the server-side values and activate
makes those values available to use in the app. It’s often convenient to call them together, but you can call them separately, too.
func setupRemoteConfig() {
let remoteConfig = RemoteConfig.remoteConfig()
remoteConfig.fetchAndActivate()
}
After calling fetchAndActivate
, you can use Remote Config’s real-time API to add a listener for new server-side values.
Behind the scenes in the SDK, add a listener by calling addOnConfigUpdateListener
, which opens a connection to the Remote Config backend. As soon as a new version of the config is published, the backend sends a signal to the SDK, which will automatically fetch the new config values. Multiple listeners will re-use the same connection.
addOnConfigUpdateListener
’s completion is called whenever parameter values from the latest published version on the Remote Config server differ from the ones currently activated in your app.
To catch any errors, add a guard here and, if your feature flag parameter key has been updated, call activate
to make the latest parameter values available to use in your app
Tip: You can use activate
strategically in your app to avoid surprising users with unexpected UI changes.
func setupRemoteConfig() {
let remoteConfig = RemoteConfig.remoteConfig()
remoteConfig.fetchAndActivate()
remoteConfig.addOnConfigUpdateListener { configUpdate, error in
guard let configUpdate, error == nil else {
print("Error listening for config updates.")
return
}
if configUpdate.updatedKeys.contains("enable_recipe_difficulty") {
remoteConfig.activate()
}
}
}
We’re now pulling the value from the Remote Config server but we haven’t actually used it anywhere! So let’s use the parameter named enable_recipe_difficulty
to access the boolean value and use it to conditionally look up the recipe difficulty. We can then use that to display its new label instead of the original recipe time.
Now, whenever the view appears it’ll be updated with the latest flag value.
struct RecipeTileView: View {
var recipe: Recipe
@RemoteConfigProperty(
key: "enable_recipe_difficulty", fallback: false)
var enableRecipeDifficulty: Bool
var body: some View {
Label("(enableRecipeDifficulty ? recipe.difficulty : recipe.time)",
systemImage: "frying.pan")
}
}
With this flag wired up in your app, you can now finish the development work on the recipe difficulty feature without impacting your users, even if you ship the feature code in some app versions and not in others.
Expose the new feature to your development and test teams
But wait…since the flag is false by default right now, how can you and the rest of your development and test teams access the feature while you build and test your app?
Google Analytics user properties to the rescue! Google Analytics user properties are per-user key-value pairs assigned by the Analytics SDK.
User properties describe the users of your app, and you can set custom properties on-demand to fit your app’s needs. For example, you could set a user’s favorite food to pancakes. In this case, you can create a user property called development_user
and set it to true
when the app runs in development mode.
func setDevelopmentUser() {
#if DEBUG
Analytics.setUserProperty("true", forName: "development_user")
#endif
}
Then, you’ll set the feature flag’s value to true
for developers by setting everything up on the Remote Config server side and testing in the app.
Open the Remote Config page in the Firebase console and edit the enable_recipe_difficulty
parameter. Add a new conditional value and name it FireRecipe Dev Team
. Then, under Applies if…, select User property and select the development_user “true” user property.
Now, when this condition is met, set the feature flag’s value to true
so the team can test the feature. Save and publish the updated config with the new value.
In the FireRecipe app, notice that the recipe time is still visible since you don’t want the UI to change unexpectedly when the user is looking at it. But as soon as you navigate to a different view, the new recipe difficulty appears.
Is it time to launch your feature?
After a lot of iteration and testing, you’re ready to launch the new recipe difficulty feature. Let’s walk through how to do this.
First, make sure the version of your deployed app that contains the feature flag parameter is stable and sufficiently adopted:
- Stability ensures that you can easily identify issues in the new release and focus on fixing one bug at a time.
- Sufficient adoption ensures that you can gather enough feedback to quickly identify a successful (or buggy) launch.
Let’s say you’re ready to launch this new feature on version 1.1 of the app. You’ll first want to make sure that version is stable and widely adopted.
To check stability and adoption, navigate to Analytics > Latest Release in the Firebase console. The Latest Release page shows the percentage of users on the latest version and that version’s percentage of crash-free users.
In this case, we see that the current version has been adopted by 86% of users and has 100% crash-free users, so it’s a good time to start rolling out your new feature.
Ramp up your feature launch
Since the app is stable and the version with the recipe difficulty feature has been well-adopted for some time, you’re ready to start exposing users to the new feature.
You’ll want to increase exposure gradually to understand if the feature is successful without impacting too many users if there are issues.
A ramp up of 1%, 10%, 50%, 100% with a few days between each step is a common practice, but the exact steps and ramp up intervals will vary depending on your feature and user base.
To set this up, let’s go back to the Firebase console to start the release.
So far, we’ve been looking at the Parameters page within Remote Config. You’ll need to create a new condition for the users that should be exposed to the new feature, so go to the Conditions page and create a new condition called Recipe Difficulty % Release
.
For the targeting conditions, select User in a random percentage and select 1% of users, since that’s where you’ll start your release.
There are a few work-in-progress iterations of the feature in older versions, so you should select a minimum app version as a targeting condition, too. That will avoid exposing the unfinished work.
To target a minimum app version, first select an app, since versions are app and platform specific. Then, add a version condition using the greater than or equal operator (>=) for version 1.1.
After creating the condition, go back to the Parameters page, edit the existing parameter, and create a new conditional value for the feature flag by selecting Add new and the condition you just created. You want the feature flag to be enabled for these users, so assign a value of true
and click on Save.
Since you configured a real-time Remote Config listener in the app, as soon as you click Publish changes, the Remote Config SDK will automatically fetch this latest value and enable recipe difficulty for 1% of users!
The feature is now live and users can start interacting with it.
Monitor key metrics to gauge your feature’s success
You can monitor your new feature’s performance in the wild by checking app stability and key metrics. This way, you’ll make sure the feature lands successfully.
One of your key metrics is daily active users, which you can use to ensure that users don’t suddenly drop off. To monitor this key metric, you can check the Realtime Analytics dashboard to ensure that active users in the app are stable (and hopefully rising!). Looking good so far!
For stability, check Crashlytics, which analyzes crashes in real-time as well…and it looks like something might be wrong! There’s a crash coming from the RecipeDifficulty
class that affects users on version 1.1, the version you just rolled out to. You thought you fixed this in development, but there must still be a bug.
Roll back that change!
To stop the app from crashing for your users, quickly jump back to Remote Config, flip the feature flag back to false
, and publish the updated parameter values. Since you’re listening for real-time config updates, as soon as you publish, your customers are back in a good state, and you can see the crashes stop.
Now that the app is stable, you can debug and release a patched version.
Wow, that was an exciting launch!
What did we learn?
First, we dove deep into why developers launch their apps with feature flags, especially how they improve the app development cycle and mitigate the risk of launching new features across a large user base.
Then, we learned how we can use real-time Remote Config updates to quickly roll out–and roll back–new features and updates, enabling smooth feature launches and quick rollbacks (just in case!).
Finally, we powered that rollout/rollback feedback loop in real-time, taking great advantage of the metrics monitoring Google Analytics provides us and the stability signals that Crashlytics gives us.
As we’ve seen, feature flagging is an incredible asset in the app development toolkit, enabling you to move faster and launch confidently. Remote Config makes it easy to adopt these best practices and, combined with real-time updates, creates awesome—and safe—user experiences.
And now, you’re ready to use real-time Remote Config to implement feature flagging today to reduce production risk, gain control of your launches, and ship safely faster!
The real-time Remote Config API is available today for the Firebase Apple platforms, Android, and Flutter SDKs and for game developers using the Firebase C++ and Unity SDKs. Get started today!