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

# Implement Feature Flags (Android)

## Overview

This developer guide will assist you in configuring your Android platform for Feature Flags using the [Mixpanel Android SDK](/docs/tracking-methods/sdks/android). Feature Flags allow you to control the rollout of your features, conduct A/B testing, and manage application behavior without deploying new code.

For complete Android SDK documentation, see the [Android SDK guide](/docs/tracking-methods/sdks/android).

## Prerequisites

Before implementing Feature Flags, ensure:

* You are on an Enterprise subscription plan  and have the latest version of the SDK installed (minimum supported version is [`v8.2.4`](https://github.com/mixpanel/mixpanel-android/releases/tag/v8.2.4)). If not, please follow [this doc](/docs/quickstart/install-mixpanel) to install the SDK.
* You have your Project Token from your [Mixpanel Project Settings](/docs/orgs-and-projects/managing-projects#find-your-project-tokens)

## Flag Initialization

Initializing the SDK with feature flags enabled requires passing a `FeatureFlagOptions` configuration to `MixpanelOptions`. This enables making an outbound request to Mixpanel servers with the current user context.
The server will assign the user context to a variant for each feature flag according to how they are configured in the Mixpanel UX.

The response will include an assigned variant for each flag that the user context is in a rollout group for. If a flag is not returned, it most likely signifies that the user was either not in the rollout percentage for a flag or in the configured targeting cohort.

**Example Usage**

```java Java theme={"system"}
FeatureFlagOptions featureFlagOptions = new FeatureFlagOptions.Builder()
    .enabled(true)
    .build();

MixpanelOptions options = new MixpanelOptions.Builder()
    .featureFlagOptions(featureFlagOptions)
    .build();

MixpanelAPI mixpanel = MixpanelAPI.getInstance(context, "YOUR_PROJECT_TOKEN", false, options);
```

If your flag is configured with a Variant Assignment Key other than `distinct_id` or `device_id` for any of the feature flags in your project, then the call to initialize feature flags must include those keys.

For example, for a Variant Assignment Key, `company_id`, you would setup the SDK as follows:

```java Java theme={"system"}
JSONObject context = new JSONObject();
context.put("company_id", "X");

FeatureFlagOptions featureFlagOptions = new FeatureFlagOptions.Builder()
    .enabled(true)
    .context(context)
    .build();

MixpanelOptions options = new MixpanelOptions.Builder()
    .featureFlagOptions(featureFlagOptions)
    .build();

MixpanelAPI mixpanel = MixpanelAPI.getInstance(getApplicationContext(), "YOUR_PROJECT_TOKEN", false, options);
```

If you are using Runtime Targeting in any of the feature flags in your project, then any properties that you use in targeting should be included in a `custom_properties` node within the context:

```java theme={"system"}
JSONObject customProperties = new JSONObject();
customProperties.put("platform", "android");

JSONObject context = new JSONObject();
context.put("company_id", "X");
context.put("custom_properties", customProperties);

FeatureFlagOptions featureFlagOptions = new FeatureFlagOptions.Builder()
    .enabled(true)
    .context(context)
    .build();

MixpanelOptions options = new MixpanelOptions.Builder()
    .featureFlagOptions(featureFlagOptions)
    .build();

MixpanelAPI mixpanel = MixpanelAPI.getInstance(getApplicationContext(), "YOUR_PROJECT_TOKEN", false, options);
```

## Flag Persistence

Minimum supported SDK version: [`v8.7.0`](https://github.com/mixpanel/mixpanel-android/releases/tag/v8.7.0)

By default (`networkOnly`), the SDK fetches fresh flag assignments and waits for the network response before variant calls complete. You can enable persistence to persist assignments to SharedPreferences so they are available immediately on subsequent launches.

Configure a `variantLookupPolicy` on the `FeatureFlagOptions` builder:

**`networkFirst`** — The SDK waits for the network on every launch. If the network call fails, the persisted value is returned as a fallback. Async getter calls block until the fetch completes (or the fallback is resolved).

```java Java theme={"system"}
FeatureFlagOptions featureFlagOptions = new FeatureFlagOptions.Builder()
    .enabled(true)
    .variantLookupPolicy(VariantLookupPolicy.networkFirst())
    .build();

// getVariantValue waits for the network. Falls back to persisted value if network fails.
mixpanel.flags.getVariantValue("my-feature-flag", "control", new FlagCompletionCallback<Object>() {
    @Override
    public void onComplete(Object value) {
        // value is from the network response, or from persistence if the network was unavailable
    }
});
```

**`persistenceUntilNetworkSuccess`** — Persisted variants are returned immediately on launch. Each getter call while serving persisted data triggers a background network fetch (deduplicated — only one fetch runs at a time). If the fetch fails, persisted data continues to be served and the next getter call retries the fetch. Once a fetch succeeds, the in-memory state updates and subsequent calls no longer trigger background fetches.

```java Java theme={"system"}
FeatureFlagOptions featureFlagOptions = new FeatureFlagOptions.Builder()
    .enabled(true)
    .variantLookupPolicy(VariantLookupPolicy.persistenceUntilNetworkSuccess())
    .build();

// getVariantValue returns immediately from persistence. Each call triggers a background fetch
// until a network response succeeds and replaces the persisted state.
mixpanel.flags.getVariantValue("my-feature-flag", "control", new FlagCompletionCallback<Object>() {
    @Override
    public void onComplete(Object value) {
        // value is from persistence; background fetch retries on each call until network succeeds
    }
});
```

To customize the TTL (default: 24 hours), pass the value in milliseconds:

```java Java theme={"system"}
long ttlMs = 12 * 60 * 60 * 1000L; // 12 hours
FeatureFlagOptions featureFlagOptions = new FeatureFlagOptions.Builder()
    .enabled(true)
    .variantLookupPolicy(VariantLookupPolicy.networkFirst(ttlMs))
    .build();
```

### Variant source

Every `MixpanelFlagVariant` carries a `source` field indicating where the value came from: `Source.Network`, `Source.Persistence`, or `Source.Fallback`. Use `getVariant()` to receive the full variant object.

```java Java theme={"system"}
MixpanelFlagVariant fallback = new MixpanelFlagVariant("control", "control");
mixpanel.flags.getVariant("my-feature-flag", fallback, new FlagCompletionCallback<MixpanelFlagVariant>() {
    @Override
    public void onComplete(MixpanelFlagVariant variant) {
        Object value = variant.value;
        MixpanelFlagVariant.Source source = variant.source; // Network, Persistence, or Fallback
    }
});
```

### `$experiment_started` properties

When a variant is served, the `$experiment_started` exposure event includes:

* `$variant_source` — `"network"` or `"persistence"`
* `$persisted_at_in_ms` — epoch ms when the variant set was persisted *(persistence only)*
* `$ttl_in_ms` — the configured TTL in ms *(persistence only)*

<Note>
  Persisted variants are tied to the current `distinct_id`. Calling `identify()` with a new ID or calling `reset()` clears persisted data and triggers a fresh fetch.
</Note>

## Flag Reload

Following initialization, you can reload feature flag assignments in a couple of ways:

1. After a user logs in or out of your application and you call `identify`, a feature flag reload will be triggered.

```java theme={"system"}
String updatedDistinctId = "";
// Reload after login
mixpanel.identify(updatedDistinctId);
```

2. To refresh flag variants that may have changed during the lifetime of your app, you can manually reload flags:

```java theme={"system"}
// Manually reload flags if desired
mixpanel.flags.loadFlags();
```

3. To update the feature flags context after initialization and trigger a reload with the new values, use `setContext`. This completely replaces the previously set custom context.

```java theme={"system"}
Map<String, Object> newContext = new HashMap<>();
newContext.put("company_id", "Y");

Map<String, Object> customProperties = new HashMap<>();
customProperties.put("platform", "android");
newContext.put("custom_properties", customProperties);

mixpanel.flags.setContext(newContext, new FlagCompletionCallback<Boolean>() {
    @Override
    public void onComplete(Boolean success) {
        // Flags have been re-fetched with the new context
    }
});
```

4. Calling `reset()` clears all feature flag assignments from memory and triggers a new fetch for the updated `distinct_id`. Requires SDK version [`v8.7.0`](https://github.com/mixpanel/mixpanel-android/releases/tag/v8.7.0) or later.

```java theme={"system"}
mixpanel.reset();
// Flags are cleared immediately. A new fetch begins for the anonymous distinct_id.
```

## Deferred Flag Loading

By default, feature flags are automatically fetched when the app first enters the foreground (`prefetchFlags` defaults to `true`). If your workflow requires calling `identify()` before the first flag fetch — for example, to ensure flags are evaluated against the correct user — you can defer automatic loading by setting `prefetchFlags(false)`:

```java theme={"system"}
FeatureFlagOptions featureFlagOptions = new FeatureFlagOptions.Builder()
    .enabled(true)
    .prefetchFlags(false)
    .build();

MixpanelOptions options = new MixpanelOptions.Builder()
    .featureFlagOptions(featureFlagOptions)
    .build();

MixpanelAPI mixpanel = MixpanelAPI.getInstance(context, "YOUR_PROJECT_TOKEN", false, options);

// Flags will not be fetched until identify() or loadFlags() is called
mixpanel.identify("user-123");
```

When `prefetchFlags` is set to `false`, flags will not be loaded until you explicitly call `identify()` or `mixpanel.flags.loadFlags()`.

## Flag Evaluation

Lookup the assigned value for a feature flag.
This action triggers tracking an exposure event, `$experiment_started` to your Mixpanel project *if* the user context is in a rollout group for the feature flag.

### Asynchronous Flag Variant Retrieval

**Experiment Flags: Get Variant Value**

```java theme={"system"}
// Get just the flag value asynchronously
String fallback = "control"; // the value to use if the user doesn't match any of the flag's rollout rules
mixpanel.flags.getVariantValue("my-feature-flag", fallback, new FlagCompletionCallback<Object>() {
    @Override
    public void onComplete(Object value) {
        // This runs on the main thread
        if (value.equals("variant_a")) {
            showExperienceForVariantA();
        } else if (value.equals("variant_b")) {
            showExperienceForVariantB();
        } else {
            showDefaultExperience();
        }
    }
});
```

**FeatureGates: Check if Flag is Enabled/Disabled**

```java theme={"system"}
// Check if a boolean flag is enabled asynchronously
boolean fallback = false; // the value to use if the user doesn't match any of the flag's rollout rules
mixpanel.flags.isEnabled("my-boolean-flag", fallback, new FlagCompletionCallback<Boolean>() {
    @Override
    public void onComplete(Boolean isEnabled) {
        // This runs on the main thread
        if (isEnabled) {
            showNewFeature();
        } else {
            showOldFeature();
        }
    }
});
```

### Synchronous Flag Variant Retrieval

**Experiment Flags: Get Variant Value**

```java theme={"system"}
// Get just the flag value synchronously
String fallback = "control"; // the value to use if the user doesn't match any of the flag's rollout rules
Object flagValue = mixpanel.flags.getVariantValueSync("my-feature-flag", fallback);

// Use flag value in your application logic
if (flagValue.equals("variant_a")) {
    showExperienceForVariantA();
} else if (flagValue.equals("variant_b")) {
    showExperienceForVariantB();
} else {
    showDefaultExperience();
}
```

**Feature Gates: Check if Flag is Enabled/Disabled**

```java theme={"system"}
// Check if a boolean flag is enabled
boolean fallback = false; // the value to use if the user doesn't match any of the flag's rollout rules
boolean isEnabled = mixpanel.flags.isEnabledSync("my-boolean-flag", fallback);

if (isEnabled) {
    showNewFeature();
} else {
    showOldFeature();
}
```

### Get All Flag Assignments

You can retrieve all feature flag assignments at once. These methods do **not** trigger `$experiment_started` tracking events.

**Synchronous**

`getAllVariantsSync()` returns a `Map<String, MixpanelFlagVariant>` containing all current flag assignments. If flags have not been loaded yet, it returns an empty map.

```java theme={"system"}
Map<String, MixpanelFlagVariant> allFlags = mixpanel.flags.getAllVariantsSync();

for (Map.Entry<String, MixpanelFlagVariant> entry : allFlags.entrySet()) {
    Log.d("Flags", entry.getKey() + ": " + entry.getValue().value);
}
```

**Asynchronous**

`getAllVariants()` returns a `Map<String, MixpanelFlagVariant>` via a callback. If flags have not been loaded yet, it will attempt to fetch them before returning.

```java theme={"system"}
mixpanel.flags.getAllVariants(new FlagCompletionCallback<Map<String, MixpanelFlagVariant>>() {
    @Override
    public void onComplete(Map<String, MixpanelFlagVariant> allFlags) {
        for (Map.Entry<String, MixpanelFlagVariant> entry : allFlags.entrySet()) {
            Log.d("Flags", entry.getKey() + ": " + entry.getValue().value);
        }
    }
});
```

## Frequently Asked Questions

### What if I'm not receiving any flags on SDK initialization?

1. **Check your project token**:

* Ensure you're using the correct project token from your [Mixpanel project settings](/docs/orgs-and-projects/managing-projects#find-your-project-tokens)

2. **Review flag configuration**:

* Make sure your feature flag is enabled
* Check the flag's rollout percentage
  * User contexts that are not assigned to the rollout percentage will not receive flags
* If you are using a targeting cohort, verify on the mixpanel 'Users' page that the user's `distinct_id` is a member of that cohort.

3. **Review SDK parameters**:

* Ensure `FeatureFlagOptions` is configured with `.enabled(true)` and passed to `MixpanelOptions`
* If using a custom Variant Assignment Key, ensure it is included in the `FeatureFlagOptions` `.context()` JSONObject
* If using Runtime Targeting, ensure all properties used in targeting are included in the `custom_properties` object within the `FeatureFlagOptions` context

4. **Check flags readiness**: Use `areFlagsReady()` to check if flags have been loaded before making synchronous calls
5. **Enable debug logging**: Check Android logs for detailed information about flag requests and responses
