> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appdna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Android Feature Flags

> Toggle features on and off without app updates

<Info>
  **Supported on:** Android SDK `1.0.33+`
</Info>

The feature flags module lets you enable or disable features remotely from the AppDNA Console. Flags are evaluated locally from cached config — no network call, no latency.

## Check a Flag

```kotlin theme={null}
if (AppDNA.features.isEnabled("dark_mode")) {
    enableDarkMode()
}
```

`isEnabled()` returns `false` by default if the flag does not exist or config has not loaded. The top-level convenience method `AppDNA.isFeatureEnabled("dark_mode")` is equivalent.

## Read a Variant Value

Some flags carry a typed payload beyond on/off (e.g., a tier name or rollout percentage). Read the value with `getVariant`:

```kotlin theme={null}
val tier = AppDNA.features.getVariant("pricing_tier") as? String
val percentage = (AppDNA.features.getVariant("rollout_percent") as? Number)?.toInt()
```

Returns `Any?` (string, number, boolean, list, or map). Cast to the type the Console stored.

## Module Access

```kotlin theme={null}
val features = AppDNA.features
```

### Module Methods

| Method       | Signature                                             | Description                                                                 |
| ------------ | ----------------------------------------------------- | --------------------------------------------------------------------------- |
| `isEnabled`  | `isEnabled(flag: String): Boolean`                    | Check if a feature flag is enabled                                          |
| `getVariant` | `getVariant(flag: String): Any?`                      | Get the typed value for a flag (string/number/bool/map)                     |
| `onChanged`  | `onChanged(callback: (Map<String, Boolean>) -> Unit)` | Register a callback for flag changes (not yet wired on Android — see below) |

## Listen for Changes

<Warning>
  `AppDNA.features.onChanged` is **not yet wired on Android**. The callback is accepted and stored, but the underlying change listener is a stub that never fires, so your callback will not be invoked when the remote config updates. Do not rely on it to react to live config changes on Android today. Instead, re-read flags with `isEnabled` / `getVariant` at the points where you need current values (for example, on app foreground or screen entry).
</Warning>

The intended usage — once wired — is to register a callback that reacts when flags change after a live config update:

```kotlin theme={null}
// NOTE: on Android this callback is not currently invoked (stub — see warning above).
AppDNA.features.onChanged { flags ->
    if (flags["maintenance_mode"] == true) {
        showMaintenanceScreen()
    }
}
```

When wired, the callback receives the full `Map<String, Boolean>` snapshot — not just changed keys. Re-read whichever flags you care about and react accordingly.

## Using Flags with Experiments

Feature flags and experiments work together. Gate a feature behind a flag, then run an experiment to test its impact:

```kotlin theme={null}
if (AppDNA.features.isEnabled("new_workout_ui")) {
    val variant = AppDNA.experiments.getVariant("workout_ui_test")

    when (variant) {
        "compact" -> showCompactWorkoutUI()
        "detailed" -> showDetailedWorkoutUI()
        else -> showDefaultWorkoutUI()
    }
} else {
    showLegacyWorkoutUI()
}
```

## Full Example

```kotlin theme={null}
import ai.appdna.sdk.AppDNA
import ai.appdna.sdk.paywalls.PaywallContext
import android.app.Activity

class FeatureGate(private val activity: Activity) {

    fun checkAccess(feature: String, onLocked: () -> Unit, onUnlocked: () -> Unit) {
        if (AppDNA.features.isEnabled(feature)) {
            onUnlocked()
        } else {
            onLocked()
        }
    }
}

// Usage
val gate = FeatureGate(activity)

gate.checkAccess(
    feature = "ai_suggestions",
    onLocked = {
        AppDNA.presentPaywall(
            activity = activity,
            id = "premium_paywall",
            context = PaywallContext(placement = "feature_gate"),
        )
    },
    onUnlocked = {
        showAiSuggestions()
    },
)
```

<Note>
  Feature flags are managed in the Console under **Settings → Feature Flags**. Toggle a flag on or off and it takes effect on the next SDK config refresh (within the TTL window, default 1 hour) or immediately via the live Firestore listener.
</Note>

## Next Steps

* Combine with [Experiments](/sdks/android/experiments) for A/B-tested rollouts
* Use [Remote Config](/sdks/android/remote-config) for richer value shapes
* Configure [Offline Support](/sdks/android/offline) for cache TTL behavior
