Firebase Remote Config: in-app default parameter values
Learn how to properly configure the in-app default values for your Firebase Remote Config parameters

In-app default parameter values concept
When using Firebase Remote Config, you define one or more parameters (key-value pairs) and provide in-app default values for those parameters. You can override in-app default values by defining server-side parameter values.
You can set in-app default parameter values in the remoteConfig object, so that your app behaves as intended before it connects to the Remote Config backend, and so that default values are available if none are set in the backend.
Firebase.remoteConfig.apply {
setDefaultsAsync(mapOf(
"key" to "default_value"
))
}Firebase also supports defining those default values using an XML. I don’t recommend that approach because makes the modularization of your remote config parameters harder.
Wisely pick your default values
It’s very important to wisely pick the correct default value for each parameter. You should always use a value that represents the more stable configuration for your app.
For example, suppose you have a stable algorithm working on production and you decide to refactor it to clean up and improve your source code. You could add a remote config parameter to decide which algorithm to use: the old one or the new one. So, you can gradually roll out the new one and measure the impact on your users. In this case, the old (and stable) algorithm should be referenced on the in-app default value for the remote config parameter. This approach guarantees you always use the old algorithm if the user couldn’t fetch the server-side value yet.
Defining defaults is not enough
But defining your in-app defaults is not enough. Let’s understand why.
In your app, parameter values are returned by get methods according to the following priority list:
- If a value was fetched from the backend and then activated, the app uses the fetched value. Activated parameter values are persistent.
- If no value was fetched from the backend, or if values fetched from the Remote Config backend have not been activated, the app uses the in-app default value.
- If no in-app default value has been set, the app uses a static type value (such as
0forintandfalseforboolean).
The last item causes an issue in certain situations (even if you correctly defined the in-app default values). Given that the setDefaultsAsync is asynchronous, after installing the app for the first time, you could try to get a remote config parameter even before your defaults are applied and cached. So, the static type values will be used (such as 0 for int and false for boolean), which could be different to your in-app default values.
The proposed solution
The following proposal fixes two problems:
- devs forget to assign in-app default values for all the parameters
- the asynchronous application of in-app default values
It also provides some other advantages:
- provides an easier way to modularize your parameters, so they are located in the proper modules.
- consolidate the parameters keys constants avoiding duplications
- makes your codes more testable
- add useful logging about the parameters
You just need to follow these 5 steps:
1. Define a RemoteConfigParameter interface
Define a RemoteConfigParameter interface to be implemented by all your remote config parameters. This interface forces you to define a default value for each parameter.
package com.dipien.firebase.remoteconfig
interface RemoteConfigParameter {
fun getKey(): String
fun getDefaultValue(): Any
}2. Implement the RemoteConfigParameter interface
Implement the RemoteConfigParameter interface with your parameters. Using an enum could be a good idea. You can have multiple enums, so you can group your related parameters and put them on the proper modules.
enum class SampleRemoteConfigParameter constructor(private val defaultValue: Any) : RemoteConfigParameter {
SAMPLE_BOOLEAN_PARAMETER(true),
SAMPLE_STRING_PARAMETER("value1"),
SAMPLE_LONG_PARAMETER(5),
SAMPLE_DOUBLE_PARAMETER(1.2);
override fun getKey(): String {
return name.lowercase()
}
override fun getDefaultValue(): Any {
return defaultValue
}
}3. Define a StaticFirebaseRemoteConfigValue
This class will return the default values defined on the RemoteConfigParameter. It will be used in the next step.
package com.dipien.firebase.remoteconfig
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue
class StaticFirebaseRemoteConfigValue(private val remoteConfigParameter: RemoteConfigParameter) : FirebaseRemoteConfigValue {
override fun asLong(): Long {
return remoteConfigParameter.getDefaultValue().toString().toLong()
}
override fun asDouble(): Double {
return remoteConfigParameter.getDefaultValue().toString().toDouble()
}
override fun asString(): String {
return remoteConfigParameter.getDefaultValue().toString()
}
override fun asByteArray(): ByteArray {
return remoteConfigParameter.getDefaultValue().toString().toByteArray()
}
override fun asBoolean(): Boolean {
return remoteConfigParameter.getDefaultValue().toString().toBoolean()
}
override fun getSource(): Int {
return FirebaseRemoteConfig.VALUE_SOURCE_STATIC
}
}4. Define a RemoteConfigLoader
Define a RemoteConfigLoader to initialize the remote config and access the remote config values.
package com.dipien.firebase.remoteconfig
import android.content.Context
import android.util.Log
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue
import com.google.firebase.remoteconfig.ktx.remoteConfig
open class FirebaseRemoteConfigLoader(
private val applicationContext: Context,
private val remoteConfigParameters: Set<RemoteConfigParameter>
) {
companion object {
private val TAG = FirebaseRemoteConfigLoader::class.simpleName
}
private val remoteConfig: FirebaseRemoteConfig by lazy {
Firebase.remoteConfig.apply {
val defaults = mutableMapOf<String, Any>()
remoteConfigParameters.forEach {
defaults[it.getKey()] = it.getDefaultValue()
}
setDefaultsAsync(defaults)
}
}
override fun getString(remoteConfigParameter: RemoteConfigParameter): String {
return getValue(remoteConfigParameter).asString()
}
override fun getStringList(remoteConfigParameter: RemoteConfigParameter): List<String> {
return getString(remoteConfigParameter).split(STRING_LIST_SEPARATOR)
}
override fun getBoolean(remoteConfigParameter: RemoteConfigParameter): Boolean {
return getValue(remoteConfigParameter).asBoolean()
}
override fun getLong(remoteConfigParameter: RemoteConfigParameter): Long {
return getValue(remoteConfigParameter).asLong()
}
override fun getDouble(remoteConfigParameter: RemoteConfigParameter): Double {
return getValue(remoteConfigParameter).asDouble()
}
private fun getValue(parameter: RemoteConfigParameter): FirebaseRemoteConfigValue {
if (!remoteConfigParameters.contains(parameter)) {
val message = "The Firebase Remote Config Parameter [${parameter.getKey()}] default value is not registered"
Log.e(TAG, message)
FirebaseCrashlytics.getInstance().recordException(RuntimeException(message))
}
var configValue = remoteConfig.getValue(parameter.getKey())
if (configValue.source == FirebaseRemoteConfig.VALUE_SOURCE_STATIC) {
configValue = StaticFirebaseRemoteConfigValue(parameter)
}
Log.i(TAG, "Read Firebase Remote Config Parameter. Key [${parameter.getKey()}] | Value [${configValue.asString()}] | Source [${configValue.source}]")
return configValue
}
}The key to solving the async issue is the following. You can ask Firebase about the source of each parameter value:
// Indicates that the value returned is the static default value.
public static final int VALUE_SOURCE_STATIC = 0;
// Indicates that the value returned was retrieved from the defaults
// set by the client.
public static final int VALUE_SOURCE_DEFAULT = 1;
// Indicates that the value returned was retrieved from the Firebase
// Remote Config Server.
public static final int VALUE_SOURCE_REMOTE = 2;So, every time Firebase returns VALUE_SOURCE_STATIC, we ignore the value returned by Firebase and use the default defined in the enum instead. That’s why we have the StaticFirebaseRemoteConfigValue class.
Some other considerations about this loader:
- If you use
Dagger/Hilt, I recommend annotating theRemoteConfigLoaderand injecting it into the places you need to use remote config. You could also define an interface and create a fake loader for your tests. - The
RemoteConfigLoaderreceives in the constructor a set with all the parameters. Remember to send all the enum values here, if you have more than once. - If you ask for the value of a parameter not passed in the constructor, it will report an exception on Crashlytics
- Every time you ask for the value of a parameter, it will be logged on Logcat, so you can see the value and the source.
5. Use the RemoteConfigLoader
Use the RemoteConfigLoader to access the remote config parameter values.
For example:
val value = remoteConfigLoader.getString(
SampleRemoteConfigParameter.SAMPLE_STRING_PARAMETER)
Support Us
There are different ways to support our work:
Related Articles
If you enjoyed this article, you might get value out of these ones as well!






