# Sample App The sample is a multi-module KMP application demonstrating the full Featured plugin workflow across three independent feature modules, a shared aggregator, and three thin platform shells. ## Module map | Module | Role | Flags | |---|---|---| | `:sample:feature-checkout` | Feature module | `new_checkout` (local Boolean, default `false`), `checkout_variant` (local enum `CheckoutVariant`, default `LEGACY`) | | `:sample:feature-promotions` | Feature module | `promo_banner_enabled` (remote Boolean, default `false`) | | `:sample:feature-ui` | Feature module | `main_button_red` (local Boolean, default `true`), `new_feature_section_enabled` (local Boolean, default `true`) | | `:sample:shared` | Aggregator + UI | Applies `dev.androidbroadcast.featured.application`; no flag declarations of its own. Contains Compose UI (`FeaturedSample`, `SampleApp`) and `SampleViewModel`. | | `:sample:android-app` | Android shell | Activity; wires `DataStoreConfigValueProvider` as the local provider and mounts `FeatureFlagsDebugScreen`. | | `:sample:desktop` | JVM shell | `main()` entry point; uses `InMemoryConfigValueProvider`. | | `iosApp/` | iOS shell | Xcode project consuming `FeaturedSampleApp.framework` (static, produced by `:sample:shared`). | ## Observe-bridge pattern Each `:sample:feature-*` module ships `*FlagObservers.kt` — public `ConfigValues` extension functions that expose the module's flags as `Flow`s and suspend setters. UI consumers call these instead of reaching into `GeneratedLocalFlags*` / `GeneratedRemoteFlags*` directly. Example from `:sample:feature-checkout` (`CheckoutFlagObservers.kt`): ```kotlin public fun ConfigValues.newCheckoutFlow(): Flow = observe(GeneratedLocalFlagsSampleFeatureCheckout.newCheckout).map { it.value } public fun ConfigValues.checkoutVariantFlow(): Flow = observe(GeneratedLocalFlagsSampleFeatureCheckout.checkoutVariant).map { it.value } public suspend fun ConfigValues.setNewCheckout(value: Boolean) { override(GeneratedLocalFlagsSampleFeatureCheckout.newCheckout, value) } ``` `SampleViewModel` in `:sample:shared` then consumes these extensions: ```kotlin val newCheckout: StateFlow = configValues.newCheckoutFlow() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L), false) ``` ## Aggregation in :sample:shared `:sample:shared` declares `featuredAggregation` dependencies for all three feature modules and wires the generated output directory into `commonMain`: ```kotlin // sample/shared/build.gradle.kts plugins { id("dev.androidbroadcast.featured.application") } dependencies { featuredAggregation(project(":sample:feature-checkout")) featuredAggregation(project(":sample:feature-promotions")) featuredAggregation(project(":sample:feature-ui")) } sourceSets.commonMain.get().kotlin.srcDir( tasks.named("generateFeaturedRegistry").map { it.outputs.files.singleFile.parentFile }, ) ``` The plugin generates `object GeneratedFeaturedRegistry { val all: List> }` which is passed to `FeatureFlagsDebugScreen`: ```kotlin FeatureFlagsDebugScreen( configValues = configValues, registry = GeneratedFeaturedRegistry.all, ) ``` ## How to add a flag 1. Edit the relevant feature module's `build.gradle.kts` — add a declaration inside `featured { localFlags { ... } }` or `featured { remoteFlags { ... } }`. 2. Add a public observer / setter in `*FlagObservers.kt` referencing the generated `GeneratedLocalFlags*` or `GeneratedRemoteFlags*` object. 3. If the UI needs it, expose a `StateFlow` + action in `SampleViewModel`. For switching providers (DataStore, SharedPreferences, Firebase Remote Config), see [Providers](Providers.md).