Skip to content

Debug UI

kirich1409 edited this page Jun 13, 2026 · 4 revisions

Debug UI

Featured ships a Compose-based debug screen that lets developers and QA override flag values at runtime without recompiling. Include it in debug builds only.

Dependencies

// build.gradle.kts
debugImplementation("dev.androidbroadcast.featured:featured-debug-ui")

Never include this in implementation or releaseImplementation — it is intended exclusively for debug builds.

Step 1 — Build the registry

FeatureFlagsDebugScreen requires an explicit List<ConfigParam<*>> that enumerates every flag you want visible in the debug screen.

Recommended — aggregator plugin (multi-module projects):

Apply the dev.androidbroadcast.featured.application plugin in your app module and declare each feature module as an aggregation dependency:

// :app/build.gradle.kts
plugins {
    id("dev.androidbroadcast.featured")
    id("dev.androidbroadcast.featured.application")
}

dependencies {
    featuredAggregation(project(":feature-checkout"))
    featuredAggregation(project(":feature-profile"))
    // Modules with enum flags also need a regular implementation dep:
    implementation(project(":feature-checkout"))
}

// Wire the generated source set:
kotlin.sourceSets.commonMain.kotlin.srcDir(
    layout.buildDirectory.dir("generated/featured/commonMain"),
)

The plugin generates object GeneratedFeaturedRegistry { val all: List<ConfigParam<*>> } at build time, aggregating flags from all declared modules.

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

val registry = listOf(MyFlags.flagA, MyFlags.flagB, MyFlags.flagC)

Step 2 — Show the debug screen

Navigate to FeatureFlagsDebugScreen from your in-app debug menu (a drawer, a shake gesture, a long-press shortcut, or a dedicated debug activity):

import dev.androidbroadcast.featured.debugui.FeatureFlagsDebugScreen

@Composable
fun DebugMenuScreen(configValues: ConfigValues) {
    FeatureFlagsDebugScreen(
        configValues = configValues,
        registry = GeneratedFeaturedRegistry.all,
    )
}

The screen lists all registry flags with their current values and sources. Tapping a flag opens an inline editor to set an override. The override is written through configValues.override(…) and persists until configValues.resetOverride(…) or configValues.clearOverrides() is called.

Search and filters

The debug screen includes a search bar and a filter chip in the top bar:

  • Search — type to filter the list by flag key or category. Results update immediately.
  • Overridden only — show only flags that currently have a local override applied.

Both UI states survive configuration changes (process death excluded).

Reset all overrides

The top bar exposes a Reset all action. Tapping it shows a confirmation dialog; confirming clears every override via configValues.clearOverrides() and surfaces an undo snackbar.

Error callback

Pass onError to observe non-fatal internal errors (provider failures, override write errors, reset failures). The screen recovers from all of them without crashing.

FeatureFlagsDebugScreen(
    configValues = configValues,
    registry = GeneratedFeaturedRegistry.all,
    onError = { e -> Timber.w(e, "Featured debug-ui error") },
)

Default behavior: prints the full stack trace to stdout via println. Pass onError = {} to silence entirely. The callback must not throw — any exception thrown from it is swallowed.

Full signature:

@Composable
fun FeatureFlagsDebugScreen(
    configValues: ConfigValues,
    registry: List<ConfigParam<*>>,
    onError: (Throwable) -> Unit = { e -> println("Featured debug-ui: ${e.stackTraceToString()}") },
    modifier: Modifier = Modifier,
)

Screenshots

Flag list

All 5 registered flags grouped by category (ui, Other, promotions, checkout). Boolean flags show a toggle with their source badge (LOCAL or DEFAULT).

Feature Flags debug screen — full list

Overriding a flag

main_button_red toggled to OFF. The toggle turns grey, the LOCAL badge stays, and a Reset link appears below to restore the default.

Feature Flags debug screen — main_button_red overridden to false

Clone this wiki locally