> ## 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.

# Flutter Feature Flags

> Toggle features on and off without app updates

<Info>
  **Supported on:** iOS SDK `1.0.61+` · Android SDK `1.0.33+` · Flutter SDK `1.0.3+`
</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 on the hot path.

## Check a Flag

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

`isEnabled()` returns `false` by default if the flag does not exist or config has not loaded.

## Multi-Variant Flags

For flags that carry a string, integer, or JSON payload (rather than a simple on/off), read the value with `getVariant`:

```dart theme={null}
final variant = await AppDNA.features.getVariant("home_layout");

switch (variant) {
  case "compact":
    showCompactHome();
    break;
  case "detailed":
    showDetailedHome();
    break;
  default:
    showDefaultHome();
}
```

`getVariant` returns `dynamic` -- cast to the type you configured in the Console.

## Module Access

```dart theme={null}
final features = AppDNA.features;
```

### Module Methods

| Method       | Signature                                 | Description                                            |
| ------------ | ----------------------------------------- | ------------------------------------------------------ |
| `isEnabled`  | `Future<bool> isEnabled(String flag)`     | Check if a feature flag is enabled                     |
| `getVariant` | `Future<dynamic> getVariant(String flag)` | Get the variant payload for a multi-variant flag       |
| `onChanged`  | `void onChanged(Function callback)`       | Register a callback that fires when flag values change |

## Using Flags with Experiments

Gate a feature behind a flag, then run an experiment to test its impact:

```dart theme={null}
if (await AppDNA.features.isEnabled("new_workout_ui")) {
  final variant =
      await AppDNA.experiments.getVariant("workout_ui_test");

  switch (variant) {
    case "compact":
      showCompactWorkoutUI();
      break;
    case "detailed":
      showDetailedWorkoutUI();
      break;
    default:
      showDefaultWorkoutUI();
  }
} else {
  showLegacyWorkoutUI();
}
```

## React to Flag Changes

Register an `onChanged` callback to re-render UI when the SDK receives an updated config from the server:

```dart theme={null}
AppDNA.features.onChanged(() {
  // Reload any UI that depends on feature flags.
  reloadHomeScreen();
});
```

<Note>
  The callback fires once per refresh, not per flag. Re-read the flags you care about inside the callback.
</Note>

## Full Example

```dart theme={null}
import 'package:appdna_sdk/appdna_sdk.dart';
import 'package:flutter/widgets.dart';

class FeatureGate {
  Future<void> checkAccess(
    String feature, {
    required VoidCallback onLocked,
    required VoidCallback onUnlocked,
  }) async {
    if (await AppDNA.features.isEnabled(feature)) {
      onUnlocked();
    } else {
      onLocked();
    }
  }
}

// Usage
final gate = FeatureGate();

await gate.checkAccess(
  "ai_suggestions",
  onLocked: () {
    AppDNA.paywall.present(
      "premium_paywall",
      context: PaywallContext(placement: "feature_gate"),
    );
  },
  onUnlocked: () {
    showAISuggestions();
  },
);

// React to remote changes
AppDNA.features.onChanged(() {
  // Re-evaluate gates when flags refresh.
});
```

<Note>
  Feature flags are managed in the Console under **Settings > Feature Flags**. Toggle a flag and it takes effect on the next SDK config refresh (default 1 hour).
</Note>
