Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ jobs:
:providers:sharedpreferences:koverVerify
:providers:firebase:koverVerify
:featured-compose:koverVerify
:featured-registry:koverVerify
:featured-testing:koverVerify
- uses: actions/upload-artifact@v7
if: always()
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Removed

- `featured-registry` module — the runtime `FlagRegistry` global singleton and its `FlagRegistryDelegate` expect/actual are removed. Use `GeneratedFeaturedRegistry.all` (produced by the `dev.androidbroadcast.featured.application` plugin) or build an explicit `List<ConfigParam<*>>` instead.
- `featured-gradle-plugin` — `generateFlagRegistrar` task, `FlagRegistrarGenerator`, and `GenerateFlagRegistrarTask` are removed. Per-module `GeneratedFlagRegistrar.kt` files are no longer generated.
- Sample API — `registerSampleFlags()` is removed (was specific to the sample app's legacy wiring). The sample now uses `SampleFeatureFlags.all` directly.

### Changed

- `FeatureFlagsDebugScreen` signature is now `(configValues: ConfigValues, registry: List<ConfigParam<*>>, modifier: Modifier = Modifier)` — accepts an explicit registry list instead of reading the (removed) `FlagRegistry` singleton. Pass `GeneratedFeaturedRegistry.all` for the recommended aggregator-plugin flow, or build the list inline for small projects.

### Added

- Featured library plugin now publishes a per-module feature-flag manifest as a consumable Gradle artifact (`featuredManifest` configuration, schema v1). Existing flag-generation pipeline is unchanged. Consumer-side aggregation arrives in a follow-up release.
Expand Down
1 change: 0 additions & 1 deletion featured-bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ dependencies {
api(project(":providers:configcat"))

api(project(":featured-compose"))
api(project(":featured-registry"))
api(project(":featured-debug-ui"))
api(project(":featured-testing"))
api(project(":featured-gradle-plugin"))
Expand Down
18 changes: 14 additions & 4 deletions featured-debug-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,28 @@ Ready-made Compose screen for runtime flag inspection and override. Intended for

```kotlin
debugImplementation("dev.androidbroadcast.featured:featured-debug-ui")
debugImplementation("dev.androidbroadcast.featured:featured-registry") // required
```

## Usage

Embed `FeatureFlagsDebugScreen` behind a dev-menu, shake gesture, or debug settings screen:
Embed `FeatureFlagsDebugScreen` behind a dev-menu, shake gesture, or debug settings screen. Pass an explicit `List<ConfigParam<*>>` as the registry.

**Recommended — use the aggregator plugin** (multi-module projects): apply `dev.androidbroadcast.featured.application` in the app module and declare feature modules via `featuredAggregation(project(...))`. The plugin generates `GeneratedFeaturedRegistry.all` at build time.

```kotlin
FeatureFlagsDebugScreen(
configValues = configValues,
registry = GeneratedFeaturedRegistry.all,
)
```

**Alternative — inline list** (small / single-module projects):

```kotlin
FeatureFlagsDebugScreen(
registry = FlagRegistry.instance,
configValues = configValues,
registry = listOf(MyFlags.flagA, MyFlags.flagB),
)
```

The screen displays all registered flags grouped by category, shows the current value and its source (DEFAULT / LOCAL / REMOTE), and allows overriding any flag value in-process. Overrides are applied via `ConfigValues.override()` and survive until the app process is restarted (or `clearOverrides()` is called).
The screen displays all registry flags grouped by category, shows the current value and its source (DEFAULT / LOCAL / REMOTE), and allows overriding any flag value in-process. Overrides are applied via `ConfigValues.override()` and survive until the app process is restarted (or `clearOverrides()` is called).
1 change: 0 additions & 1 deletion featured-debug-ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(projects.core)
implementation(projects.featuredRegistry)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,11 @@ import androidx.compose.ui.unit.dp
import dev.androidbroadcast.featured.ConfigParam
import dev.androidbroadcast.featured.ConfigValue
import dev.androidbroadcast.featured.ConfigValues
import dev.androidbroadcast.featured.registry.FlagRegistry
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch

/**
* A ready-to-use debug screen that lists all feature flags registered in [FlagRegistry]
* A ready-to-use debug screen that lists all feature flags in the provided [registry]
* and allows toggling boolean flags or viewing current values for other types.
*
* Flags are grouped by [ConfigParam.category]. Each flag shows its current value, source
Expand All @@ -55,31 +54,41 @@ import kotlinx.coroutines.launch
*
* Intended for debug/internal builds only.
*
* Pass `GeneratedFeaturedRegistry.all` (from the `dev.androidbroadcast.featured.application`
* plugin) or build the list explicitly.
*
* @param configValues The [ConfigValues] instance used to read and override flag values.
* @param registry The list of [ConfigParam] instances to display. Must be a stable
* reference (a top-level `val`, an `object` property, or a `remember`-ed list).
* The screen keys its internal `LaunchedEffect` on this list via `equals` (structural).
* A freshly-allocated list on every recomposition may restart the effect; prefer a
* stable top-level `val` or `object` property for predictable behavior.
* Each [ConfigParam.key] must be unique within the list; duplicates cause a
* runtime crash in `LazyColumn` key collision.
* @param modifier Optional [Modifier] for the root composable.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@Suppress("ktlint:standard:function-naming")
public fun FeatureFlagsDebugScreen(
configValues: ConfigValues,
registry: List<ConfigParam<*>>,
modifier: Modifier = Modifier,
) {
val scope = rememberCoroutineScope()
var groupedItems by remember {
mutableStateOf<Map<String?, List<DebugFlagItem<*>>>>(emptyMap())
}

LaunchedEffect(configValues) {
val params = FlagRegistry.all()
groupedItems = groupFlagsByCategory(buildDebugItems(configValues, params))
LaunchedEffect(configValues, registry) {
groupedItems = groupFlagsByCategory(buildDebugItems(configValues, registry))

// Reactive: observe all params and refresh on any change.
// On each emission all params are re-read — acceptable for a debug-only screen.
val flows = params.map { param -> configValues.observe(param) }
val flows = registry.map { param -> configValues.observe(param) }
if (flows.isNotEmpty()) {
flows.merge().collect {
groupedItems = groupFlagsByCategory(buildDebugItems(configValues, params))
groupedItems = groupFlagsByCategory(buildDebugItems(configValues, registry))
}
}
}
Expand All @@ -97,7 +106,7 @@ public fun FeatureFlagsDebugScreen(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "No feature flags registered.",
text = "No feature flags to display.",
style = MaterialTheme.typography.bodyMedium,
)
}
Expand Down
1 change: 0 additions & 1 deletion featured-gradle-plugin/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ featured {
|------|--------|
| `resolveFeatureFlags` | `build/featured/flags.txt` |
| `generateConfigParam` | `build/generated/featured/commonMain/Generated{Local,Remote}Flags.kt` + `GeneratedFlagExtensions.kt` |
| `generateFlagRegistrar` | `build/generated/featured/GeneratedFlagRegistrar.kt` |
| `generateFeaturedProguardRules` | `build/featured/proguard-featured.pro` |
| `generateIosConstVal` | iOS constant value files |
| `generateXcconfig` | `build/featured/FeatureFlags.generated.xcconfig` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import org.gradle.api.tasks.TaskProvider

internal const val RESOLVE_FLAGS_TASK_NAME = "resolveFeatureFlags"
internal const val SCAN_ALL_TASK_NAME = "scanAllLocalFlags"
internal const val GENERATE_FLAG_REGISTRAR_TASK_NAME = "generateFlagRegistrar"
internal const val GENERATE_PROGUARD_TASK_NAME = "generateFeaturedProguardRules"
internal const val GENERATE_IOS_CONST_VAL_TASK_NAME = "generateIosConstVal"
internal const val GENERATE_XCCONFIG_TASK_NAME = "generateXcconfig"
Expand All @@ -24,8 +23,7 @@ internal const val GENERATE_CONFIG_PARAM_TASK_NAME = "generateConfigParam"
* 1. Exposes the `featured { }` DSL extension for declaring local and remote feature flags.
* 2. Generates typed `ConfigParam` objects and ergonomic `ConfigValues` extension functions.
* 3. Generates per-function R8 `-assumevalues` rules for local flags (dead-code elimination).
* 4. Generates a `GeneratedFlagRegistrar` that registers all flags with `FlagRegistry`.
* 5. Generates iOS constant-value files and xcconfig for Swift dead-code elimination.
* 4. Generates iOS constant-value files and xcconfig for Swift dead-code elimination.
*
* Usage in `build.gradle.kts`:
* ```kotlin
Expand Down Expand Up @@ -57,7 +55,6 @@ public class FeaturedPlugin : Plugin<Project> {
}

registerConfigParamTask(target, resolveTask)
registerFlagRegistrarTask(target, resolveTask)
val proguardTask = registerProguardTask(target, resolveTask)
registerIosConstValTask(target, resolveTask)
registerXcconfigTask(target, resolveTask)
Expand Down Expand Up @@ -94,22 +91,6 @@ public class FeaturedPlugin : Plugin<Project> {
}
}

private fun registerFlagRegistrarTask(
target: Project,
resolveTask: TaskProvider<ResolveFlagsTask>,
) {
target.tasks.register(GENERATE_FLAG_REGISTRAR_TASK_NAME, GenerateFlagRegistrarTask::class.java) { task ->
task.group = "featured"
task.description = "Generates GeneratedFlagRegistrar.kt for '${target.path}'."
task.scanResultFile.set(resolveTask.flatMap { it.outputFile })
task.packageName.set("dev.androidbroadcast.featured.generated")
task.outputFile.set(
target.layout.buildDirectory.file("generated/featured/GeneratedFlagRegistrar.kt"),
)
task.dependsOn(resolveTask)
}
}

private fun registerProguardTask(
target: Project,
resolveTask: TaskProvider<ResolveFlagsTask>,
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,6 @@ public data class LocalFlagEntry(
*/
public val isEnum: Boolean get() = '.' in type

/**
* Returns the Kotlin reference used in the generated `FlagRegistry.register(...)` call.
*
* - Local flags: `"GeneratedLocalFlags.propertyName"`
* - Remote flags: `"GeneratedRemoteFlags.propertyName"`
* - Blank when [propertyName] is empty (legacy data without property information).
*/
public val kotlinReference: String
get() =
when {
propertyName.isBlank() -> ""
isLocal -> "$GENERATED_LOCAL_OBJECT.$propertyName"
else -> "$GENERATED_REMOTE_OBJECT.$propertyName"
}

public companion object {
public const val FLAG_TYPE_LOCAL: String = "local"
public const val FLAG_TYPE_REMOTE: String = "remote"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import org.gradle.api.tasks.TaskAction
*
* where `propertyName` is the camelCase conversion of `key` (e.g. `dark_mode` → `darkMode`).
*
* Downstream tasks ([GenerateFlagRegistrarTask], [GenerateProguardRulesTask],
* [GenerateConfigParamTask], etc.) declare [outputFile] as their `@InputFile` to
* Downstream tasks ([GenerateProguardRulesTask], [GenerateConfigParamTask], etc.)
* declare [outputFile] as their `@InputFile` to
* establish a proper task dependency and enable configuration-cache compatibility.
*/
@CacheableTask
Expand Down
Loading
Loading