Skip to content

Providers

kirich1409 edited this page May 18, 2026 · 1 revision

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)

Built-in local providers

InMemoryConfigValueProvider

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.

DataStoreConfigValueProvider

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),
)

SharedPreferencesConfigValueProvider

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),
)

JavaPreferencesConfigValueProvider

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 })
)

NSUserDefaultsConfigValueProvider

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.

Built-in remote providers

ConfigCatConfigValueProvider

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.

FirebaseConfigValueProvider

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() calls fetchAndActivate() by default — values are immediately available after the call returns.
  • Pass activate = false to defer activation: configValues.fetch(activate = false).
  • A FetchException is thrown on network errors or service unavailability; implement exponential backoff for retries.

Firebase project setup:

  1. Add google-services.json (Android) or GoogleService-Info.plist (iOS) to the project.
  2. In the Firebase console, navigate to Remote Config and add parameters whose keys match ConfigParam.key values.
  3. Publish, then call configValues.fetch() at app start.

Provider resolution order

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

Writing a custom provider

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>> { … }
}

Clone this wiki locally