Featured is a type-safe, reactive feature-flag and configuration management library for Kotlin Multiplatform — Android, iOS (via SKIE), and JVM.
- Type-safe flags — declared in the Gradle DSL, accessed via generated typed extensions on
ConfigValues. No string keys, no unchecked casts. - Dead-code elimination in release builds — a flag with
default = falsemakes the guarded code unreachable. The Gradle plugin emits R8-assumevaluesrules (Android/JVM) and an xcconfig withDISABLE_<FLAG>Swift compilation conditions (iOS), so the respective compilers physically strip disabled branches from release binaries. - Reactive — every value is observable via
Flow; Compose and SwiftUI/Combine integrations included. - Multiple providers — DataStore, SharedPreferences, NSUserDefaults, JavaPreferences, Firebase Remote Config, ConfigCat, or a custom one.
- Debug UI — a ready-made Compose screen for overriding flags at runtime.
| Platform | Status |
|---|---|
| Android | Stable |
| iOS (SKIE / DCE) | Preview |
| JVM | Preview |
Preview means the platform is functional but its public API may change in minor releases without a major version bump. Stable platforms follow Semantic Versioning.
// build.gradle.kts — declare the flag
plugins {
id("dev.androidbroadcast.featured") version "<version>"
}
dependencies {
implementation(platform("dev.androidbroadcast.featured:featured-bom:<version>"))
implementation("dev.androidbroadcast.featured:featured-core")
implementation("dev.androidbroadcast.featured:featured-datastore-provider")
}
featured {
localFlags {
boolean("new_checkout", default = false) {
description = "Enable the new checkout flow"
}
}
}// Application.kt — wire up ConfigValues once
val dataStore = PreferenceDataStoreFactory.create { context.dataStoreFile("feature_flags.preferences_pb") }
val configValues = ConfigValues(
localProvider = DataStoreConfigValueProvider(dataStore),
)// Read the generated extension anywhere
val isEnabled: Boolean = configValues.isNewCheckoutEnabled()In a multi-module app, construct one ConfigValues per feature module plus one debug aggregator,
all sharing the same LocalConfigValueProvider:
// Construct one ConfigValues per feature module + one debug aggregator, all over a shared provider
val sharedLocal: LocalConfigValueProvider = defaultLocalProvider(applicationContext)
val checkoutConfig = ConfigValues(localProvider = sharedLocal)
val promotionsConfig = ConfigValues(localProvider = sharedLocal)
val uiConfig = ConfigValues(localProvider = sharedLocal)
// Debug-only aggregator that the FeatureFlagsDebugScreen drives
val debugConfig = ConfigValues(localProvider = sharedLocal)
FeatureFlagsDebugScreen(
configValues = debugConfig,
registry = GeneratedFeaturedRegistry.all,
)Each feature module owns its own ConfigValues and observes only its own flags (via public
observe-bridge extensions). The generated GeneratedLocalFlagsX / GeneratedRemoteFlagsX objects
are internal to their module — cross-module flag listing flows exclusively through
GeneratedFeaturedRegistry.all, which is built from the per-module manifests by the aggregator
plugin. The single source of truth for stored overrides is the shared LocalConfigValueProvider,
so writes from any instance propagate to every other one through its reactive observe flow.
Full documentation lives in the Wiki:
- Getting Started
- Installation
- Providers
- Release Optimization (DCE) — how flags get stripped from release binaries
- iOS Usage
- Best Practices
See CONTRIBUTING.md.
See SECURITY.md.
MIT — see LICENSE.