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

> A/B test with deterministic, offline-capable bucketing

<Info>
  **Supported on:** iOS SDK `1.0.61+` · Android SDK `1.0.33+` · Flutter SDK `1.0.3+`
</Info>

The experiments module assigns users to variants using a deterministic hash. No server call is needed -- assignment is instant, works offline, and the same user always gets the same variant.

## Get Variant

```dart theme={null}
final variant = await AppDNA.experiments.getVariant("paywall-test");

switch (variant) {
  case "control":
    showStandardPaywall();
    break;
  case "variant_a":
    showNewPaywall();
    break;
  default:
    // null, archived, or user not in audience — always handle this branch.
    showStandardPaywall();
}
```

`getVariant` is asynchronous and returns the variant string or `null` if the experiment is not found, not running, or the user is not in the target audience.

<Warning>
  Always handle the `null` / default case. Experiments can be archived or stopped at any time from the Console.
</Warning>

## Check Membership

When you only need a boolean for a specific variant, `isInVariant` is a convenient shortcut:

```dart theme={null}
if (await AppDNA.experiments.isInVariant("paywall-test", "variant_a")) {
  showNewPaywall();
} else {
  showStandardPaywall();
}
```

## Module Access

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

### Module Methods

| Method         | Signature                                                         | Description                                             |
| -------------- | ----------------------------------------------------------------- | ------------------------------------------------------- |
| `getVariant`   | `Future<String?> getVariant(String experimentId)`                 | Get the assigned variant                                |
| `isInVariant`  | `Future<bool> isInVariant(String experimentId, String variantId)` | Check if the user is in a specific variant              |
| `getExposures` | `Future<List<Map<String, dynamic>>> getExposures()`               | Read recorded experiment exposures for the current user |

## Exposure Tracking

The SDK automatically tracks an `experiment_exposure` event the first time `getVariant()` is called for a given experiment in a session. Exposure events power the metrics that the experimentation engine uses to compute results -- you do not need to fire them yourself.

To inspect what the SDK has already recorded for the current user, call `getExposures()`:

```dart theme={null}
final exposures = await AppDNA.experiments.getExposures();

for (final exposure in exposures) {
  final experimentId = exposure["experimentId"] as String?;
  final variant = exposure["variant"] as String?;
  print("Exposed to $experimentId -> $variant");
}
```

This is useful for debugging, surfacing the active variant in a developer panel, or attaching the current bucket to your own analytics events.

## Reacting to Assignment Changes

The SDK does not push assignment-change callbacks. Variant assignments are stable for a given user/experiment pair and only change when the experiment definition is updated on the server. If you need to refresh UI when the experiment configuration could have changed, re-read `getVariant` at natural moments in your app:

* On app launch and on return from background
* When you present the screen that depends on the variant
* After a known config-refreshing action (e.g. login, environment switch)

```dart theme={null}
class HomeScreenController {
  String? _paywallVariant;

  Future<void> refresh() async {
    _paywallVariant = await AppDNA.experiments.getVariant("paywall_redesign");
    // re-render UI that depends on _paywallVariant
  }
}
```

<Note>
  Assignment changes are rare. They occur only when the experiment definition changes on the server, not on every session start.
</Note>

## Experiment Lifecycle

| Status        | `getVariant()` returns        |
| ------------- | ----------------------------- |
| **Draft**     | `null` -- not visible to SDKs |
| **Running**   | Assigned variant string       |
| **Completed** | Winning variant for all users |
| **Archived**  | `null` -- removed from config |

## Full Example

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

class PaywallExperiment {
  Future<void> showPaywall() async {
    final variant =
        await AppDNA.experiments.getVariant("paywall_redesign");

    final paywallId =
        variant == "new_design" ? "paywall_v2" : "paywall_v1";

    await AppDNA.paywall.present(
      paywallId,
      context: PaywallContext(
        placement: "settings",
        customData: {
          "experiment": "paywall_redesign",
          "variant": variant ?? "control",
        },
      ),
    );
  }
}
```

<Info>
  Experiments are created in the Console under **Experiments**. The SDK uses MurmurHash3 for deterministic assignment -- the same user always gets the same variant across sessions, platforms, and offline.
</Info>
