-
Notifications
You must be signed in to change notification settings - Fork 0
Providers
ConfigValues composes one optional local provider and one optional remote provider. At least one must be non-null. Remote values take precedence over local values when both are present for the same key.
ConfigValues
├── LocalConfigValueProvider (optional, but at least one required)
└── RemoteConfigValueProvider (optional, but at least one required)
Stores overrides in a plain in-memory Map. No setup, no dependencies. Included in featured-core.
Use cases: unit tests, Compose previews, ephemeral runtime overrides.
Limitation: values are lost when the process terminates.
val configValues = ConfigValues(
localProvider = InMemoryConfigValueProvider(),
)Programmatic set and reset:
val provider = InMemoryConfigValueProvider()
provider.set(GeneratedLocalFlags.newCheckout, true) // override
provider.resetOverride(GeneratedLocalFlags.newCheckout) // revert to default
provider.clear() // remove all overrides (no Flow signal)set and resetOverride notify active observe flows immediately. clear does not emit change signals.
Persists flag overrides using Jetpack DataStore. Recommended for Android.
Dependency: featured-datastore-provider
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import dev.androidbroadcast.featured.datastore.DataStoreConfigValueProvider
val dataStore = PreferenceDataStoreFactory.create {
context.dataStoreFile("feature_flags.preferences_pb")
}
val configValues = ConfigValues(
localProvider = DataStoreConfigValueProvider(dataStore),
)Android-only. Persists overrides using SharedPreferences.
Dependency: featured-sharedpreferences-provider
import dev.androidbroadcast.featured.sharedpreferences.SharedPreferencesConfigValueProvider
val prefs = context.getSharedPreferences("feature_flags", Context.MODE_PRIVATE)
val configValues = ConfigValues(
localProvider = SharedPreferencesConfigValueProvider(prefs),
)JVM/Desktop only. Persists overrides using java.util.prefs.Preferences. Storage is OS-specific: the registry on Windows, a plist on macOS, and ~/.java on Linux.
Dependency: featured-javaprefs-provider
Supported types: String, Int, Long, Float, Double, Boolean.
import dev.androidbroadcast.featured.javaprefs.JavaPreferencesConfigValueProvider
val provider = JavaPreferencesConfigValueProvider()
// Or with a custom node:
val provider = JavaPreferencesConfigValueProvider(
node = Preferences.userRoot().node("com/example/myapp/flags")
)
val configValues = ConfigValues(localProvider = provider)Register a TypeConverter for custom types:
provider.registerConverter<Theme>(
TypeConverter(fromString = { Theme.valueOf(it) }, toString = { it.name })
)iOS/macOS only. Persists flag overrides using NSUserDefaults. Values survive process restarts and are stored in the standard user defaults domain or a custom app-group suite.
Dependency: featured-nsuserdefaults-provider
Supported types: String, Int, Long, Float, Double, Boolean.
import dev.androidbroadcast.featured.nsuserdefaults.NSUserDefaultsConfigValueProvider
// Standard user defaults
val provider = NSUserDefaultsConfigValueProvider()
// Custom suite (e.g. for App Groups)
val provider = NSUserDefaultsConfigValueProvider(suiteName = "com.example.app.flags")
val configValues = ConfigValues(localProvider = provider)set and resetOverride notify active observe flows immediately. clear removes all keys but does not emit change signals.
Wraps the ConfigCat KMP SDK. Remote values override local values when present.
Dependency: featured-configcat-provider
Supported types: Boolean, String, Int, Long, Double, Float.
import com.configcat.ConfigCatClient
import dev.androidbroadcast.featured.configcat.ConfigCatConfigValueProvider
val client = ConfigCatClient(sdkKey = "YOUR_SDK_KEY")
val provider = ConfigCatConfigValueProvider(client)
val configValues = ConfigValues(remoteProvider = provider)
// Fetch latest values — suspend function, call from a coroutine
lifecycleScope.launch { configValues.fetch() }fetch() calls ConfigCatClient.forceRefresh() and activates updated values immediately.
Wraps Firebase Remote Config. Remote values override local values when present.
Dependency: featured-firebase-provider
Supported types natively: String, Boolean, Int, Long, Double, Float. Enums are resolved by name automatically.
import dev.androidbroadcast.featured.firebase.FirebaseConfigValueProvider
val configValues = ConfigValues(
localProvider = DataStoreConfigValueProvider(dataStore),
remoteProvider = FirebaseConfigValueProvider(),
)
// Fetch and activate on app start — suspend function, call from a coroutine
lifecycleScope.launch { configValues.fetch() }Pass a custom FirebaseRemoteConfig instance if you manage the Firebase lifecycle yourself:
FirebaseConfigValueProvider(remoteConfig = FirebaseRemoteConfig.getInstance())Fetch strategy:
-
configValues.fetch()callsfetchAndActivate()by default — values are immediately available after the call returns. - Pass
activate = falseto defer activation:configValues.fetch(activate = false). - A
FetchExceptionis thrown on network errors or service unavailability; implement exponential backoff for retries.
Firebase project setup:
- Add
google-services.json(Android) orGoogleService-Info.plist(iOS) to the project. - In the Firebase console, navigate to Remote Config and add parameters whose keys match
ConfigParam.keyvalues. - Publish, then call
configValues.fetch()at app start.
When both local and remote providers return a value for the same key, remote wins. If only one provider returns a value, that value is used. If neither returns a value, the ConfigParam.defaultValue is used and the source is ConfigValue.Source.DEFAULT.
| Local | Remote | Result | Source |
|---|---|---|---|
| — | — | defaultValue |
DEFAULT |
| present | — | local value | LOCAL |
| — | present | remote value | REMOTE |
| present | present | remote value | REMOTE |
Implement LocalConfigValueProvider for a writable, observable local backend:
class MyLocalProvider : LocalConfigValueProvider {
override suspend fun <T : Any> get(param: ConfigParam<T>): ConfigValue<T>? { … }
override fun <T : Any> observe(param: ConfigParam<T>): Flow<ConfigValue<T>> { … }
override suspend fun <T : Any> set(param: ConfigParam<T>, value: T) { … }
override suspend fun <T : Any> resetOverride(param: ConfigParam<T>) { … }
override suspend fun clear() { … }
}Implement RemoteConfigValueProvider for a fetch-based remote backend:
class MyRemoteProvider : RemoteConfigValueProvider {
override suspend fun fetch(activate: Boolean) { /* fetch from your backend */ }
override suspend fun <T : Any> get(param: ConfigParam<T>): ConfigValue<T>? { … }
override fun <T : Any> observe(param: ConfigParam<T>): Flow<ConfigValue<T>> { … }
}