Firebase series · Part 1 of 4
Firebase Remote Config: Change Your App Without a New Release
Remote Config lets you change how your app looks and behaves from a console, without publishing a new version. It is a key-value store in the cloud that your app downloads and reads. This post is the overview; Google’s guide covers the exact setup.
- What: cloud key-value settings your app fetches.
- Use for: feature flags, kill switches, copy, limits, gradual rollouts.
- Key idea: ship defaults in the app, override them from the console.
- Watch out: values may arrive late. Fetch at launch and keep safe defaults.
- Not for: secrets or user data. Values are readable by the app user.
Contents
1. What it is
Normally, changing a button colour or turning a feature off means a new build, a store review and users updating. With Remote Config the app reads its settings from Firebase instead. You edit a value in the console, publish, and the app picks it up on its next fetch.
Analogy. A TV remote for your app: the TV (your code) is already built, and the remote (console) changes channels and volume from a distance.
2. How it works
Your app ships with in-app defaults. On start-up it fetches the latest values from the service, then activates them so the code reads the new ones. Fetches are throttled, so the new value may appear on the next launch rather than instantly.
3. Timing: values are not instant
A fetch is a network call. On a slow connection, in airplane mode, or on the very first launch it may be late or fail, and the throttle can also hold back a refresh. If your code assumes the new value is already there, it can read the wrong one at the worst moment, such as a kill switch that has not arrived yet. This is the main runtime risk of Remote Config.
Two terms are used below, so here is what they mean in this post:
| Term | Meaning | Remote Config example |
|---|---|---|
| App launch | The short window from when the user opens the app until the first screen is ready: process start, Application.onCreate, splash screen. | Start the fetch here, so values can be ready before the user reaches a feature. |
| Runtime | Everything after launch, while the app is open and in use: screens, taps, background work. | Code reads a value with getBoolean or getString. A fetch started at runtime may finish at any moment, or not at all. |
Launch-time fetch means asking for fresh values during app launch. Runtime risk means the app reads a value while the fetch is still running, or after it failed, and so gets the default or cached value instead of the new one.
// App launch: start the fetch early, and do not block on it
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
val rc = Firebase.remoteConfig
rc.setConfigSettingsAsync(remoteConfigSettings { minimumFetchIntervalInSeconds = 3600 })
rc.setDefaultsAsync(mapOf("new_checkout_enabled" to false)) // safe default: off
rc.fetchAndActivate() // async, may finish later
}
}
// Runtime: read with the default as the fallback
fun showCheckout() {
val useNew = Firebase.remoteConfig.getBoolean("new_checkout_enabled")
if (useNew) openNewCheckout() else openOldCheckout() // both paths must work
}
Launching the fetch early, at app start, reduces that risk, because the values usually arrive before the user reaches the screen that needs them. Early fetch does not remove the risk, so combine it with these habits:
- Fetch at launch. Call
fetchAndActivate()inApplication.onCreateor on the splash screen, not when the feature screen opens. - Ship safe defaults. Choose each default so the app is fine if nothing ever arrives. A new feature should default to off.
- Prefer last-known values. Activated values are cached on the device, so a failed fetch still leaves the previous ones.
- Do not block the UI. Wait on the splash for a short timeout (about 2 seconds) at most, then continue with what you have.
- Apply at a safe moment. A value fetched now can take effect on the next launch or screen, so the UI does not change under the user. The real-time listener can push updates while the app is running.
- Keep important rules consistent. If a flag changes mid-session, read it once per screen, not on every call.
Runtime update: a value changes while the app is open
The code above reads whatever is active. To hear about a change while the app is running, register a real-time listener. It tells you a new template was published; you then activate it and apply it at a safe moment, such as the next screen, not in the middle of a task.
Firebase.remoteConfig.addOnConfigUpdateListener(object : ConfigUpdateListener {
override fun onUpdate(update: ConfigUpdate) {
Firebase.remoteConfig.activate().addOnCompleteListener {
// Apply at a safe moment, e.g. on the next screen, not mid-task
checkoutFlag = Firebase.remoteConfig.getBoolean("new_checkout_enabled")
}
}
override fun onError(error: FirebaseRemoteConfigException) { /* keep current values */ }
})
See Google’s real-time Remote Config guide for the exact setup and requirements.
4. Conditions and rollouts
A parameter can have several values, each tied to a condition: app version, platform, country, language, a percentage of users, or a user property from Analytics. That gives you staged rollouts (5%, then 50%, then 100%) and per-market settings from one place.
Remote Config also powers A/B Testing: Firebase serves variants to different groups and measures which wins with Analytics.
Rollout timing depends on your users
Publishing in the console does not push a value to every phone at once. Each device gets it at its next successful fetch, and that depends on when the user opens the app and on the minimum fetch interval (12 hours by default). So the same rollout looks different for different apps:
- Large user base: the change spreads over hours or days as people open the app. At any moment some users are on the old value and some on the new one, so your code and backend must tolerate both. A 10% rollout reaches about 10% of users, but only as they fetch.
- Small user base: percentages are only approximate, because users are placed in the percentage at random. With 50 users, a “10%” rollout might reach 3 or 8 people. Few users also means fewer fetches, so a value can take longer to show up for the quiet ones.
- Rarely opened apps: a user who opens the app once a week sees changes about a week late.
Plan for this: raise the percentage in steps and watch Crashlytics and Analytics between steps. For time-critical changes such as a kill switch, shorten the fetch interval or use real-time Remote Config updates so running apps can pick the change up sooner. Do not rely on it as an instant switch.
5. Common uses
| Use | Example |
|---|---|
| Feature flag | Enable the new checkout for 10% of users. |
| Kill switch | Turn off a broken feature without a hotfix release. |
| Force update | Compare min_app_version with the installed version. |
| Content and copy | Seasonal banner text or promo colours. |
| Tuning | Timeouts, page sizes, retry counts. |
6. A tiny example
The shape of the Android code, after you have added the SDK as Google describes:
val rc = Firebase.remoteConfig
rc.setDefaultsAsync(mapOf("promo_banner_enabled" to false))
rc.fetchAndActivate().addOnCompleteListener {
val show = rc.getBoolean("promo_banner_enabled")
banner.isVisible = show
}
7. Limits and good habits
- Never put secrets in it. Anyone can inspect the values.
- Always set sensible defaults so the app works offline and on first launch.
- Fetches are rate-limited; use a short interval only in debug builds.
- Never assume the fetch has finished. Read values with a safe default and see section 3.
- Name keys clearly and delete flags once a rollout is finished.
- Make code safe if a value is missing or unexpected.
Official links
Setup steps change often, so follow Google’s own guides rather than a copy here.
← Back to all writing