---
title: Android and Android TV Feature Flags
description: >-
  Set up Datadog Feature Flags for Android and Android TV applications using the
  OpenFeature standard API.
breadcrumbs: >-
  Docs > Feature Flags > Client-Side Feature Flags > Android and Android TV
  Feature Flags
---

# Android and Android TV Feature Flags

{% callout %}
# Important note for users on the following Datadog sites: app.ddog-gov.com

{% alert level="danger" %}
This product is not supported for your selected [Datadog site](https://docs.datadoghq.com/getting_started/site.md). ().
{% /alert %}

{% /callout %}

## Overview{% #overview %}

This page describes how to instrument your Android or Android TV application with the Datadog Feature Flags SDK. Datadog feature flags provide a unified way to remotely control feature availability in your app, experiment safely, and deliver new experiences with confidence.

The Datadog Feature Flags SDK for Android is built on [OpenFeature](https://openfeature.dev/), an open standard for feature flag management. This guide explains how to install the SDK, configure the Datadog provider, and evaluate flags in your application.

{% alert level="info" %}
For most applications, the OpenFeature API is the recommended approach. If you need multiple independent evaluation contexts in the same application, see Direct FlagsClient Integration.
{% /alert %}

## Getting started{% #getting-started %}

Here's a minimal example to get feature flags working in your Android app:

```kotlin
// 1. Add dependencies (see Installation section)

// 2. Initialize Datadog SDK (in Application.onCreate)
val configuration = Configuration.Builder(
    clientToken = "<CLIENT_TOKEN>",
    env = "<ENV_NAME>",
    variant = "<APP_VARIANT_NAME>"
)
    .useSite(DatadogSite.)
    .build()
Datadog.initialize(this, configuration, TrackingConsent.GRANTED)

// 3. Enable Feature Flags
Flags.enable()

// 4. Create and set up the OpenFeature provider
val provider = FlagsClient.Builder().build().asOpenFeatureProvider()
OpenFeatureAPI.setProviderAndWait(provider)

// 5. Set evaluation context (who is the user)
OpenFeatureAPI.setEvaluationContext(
    ImmutableContext(
        targetingKey = "user-123",
        attributes = mapOf("tier" to Value.String("premium"))
    )
)

// 6. Evaluate flags anywhere in your app
val client = OpenFeatureAPI.getClient()
val isEnabled = client.getBooleanValue("my-feature", false)
```

The rest of this guide explains each step in detail.

## Installation{% #installation %}

Add the Datadog Feature Flags SDK and OpenFeature Provider as Gradle dependencies in your application module's `build.gradle` file:

In the `build.gradle` file:

```groovy
dependencies {
    implementation "com.datadoghq:dd-sdk-android-flags:<latest-version>"
    implementation "com.datadoghq:dd-sdk-android-flags-openfeature:<latest-version>"

    // Recommended: RUM integration drives analysis and enriches RUM session data
    implementation "com.datadoghq:dd-sdk-android-rum:<latest-version>"
}
```

## Initialize the SDK{% #initialize-the-sdk %}

Initialize Datadog as early as possible in your app lifecycle—typically in your `Application` class's `onCreate()` method. This helps ensure all feature flag evaluations and telemetry are captured correctly.

```kotlin
val configuration = Configuration.Builder(
    clientToken = "<CLIENT_TOKEN>",
    env = "<ENV_NAME>",
    variant = "<APP_VARIANT_NAME>"
)
    .useSite(DatadogSite.)
    .build()

Datadog.initialize(this, configuration, TrackingConsent.GRANTED)
```

## Enable flags{% #enable-flags %}

After initializing Datadog, enable `Flags` to attach it to the current Datadog SDK instance and prepare for provider creation and flag evaluation:

```kotlin
import com.datadog.android.flags.Flags

Flags.enable()
```

You can also pass a configuration object; see Advanced configuration.

## Create and configure the provider{% #create-and-configure-the-provider %}

Create a `FlagsClient` and convert it to an OpenFeature provider using the `asOpenFeatureProvider()` extension. Do this once during app startup:

```kotlin
import com.datadog.android.flags.FlagsClient
import com.datadog.android.flags.openfeature.asOpenFeatureProvider
import dev.openfeature.kotlin.sdk.OpenFeatureAPI

// Create and configure the provider
val provider = FlagsClient.Builder().build().asOpenFeatureProvider()

// Set it as the OpenFeature provider
OpenFeatureAPI.setProviderAndWait(provider)
```

{% alert level="info" %}
The OpenFeature provider wraps a Datadog `FlagsClient` internally. This is an implementation detail—once set up, you interact exclusively through the standard OpenFeature API.
{% /alert %}

{% alert level="warning" %}
The OpenFeature Kotlin SDK uses a single global provider and evaluation context. If you need multiple independent evaluation contexts in the same app (for example, for different users in a multi-user app), see Direct FlagsClient Integration.
{% /alert %}

## Set the evaluation context{% #set-the-evaluation-context %}

Define who or what the flag evaluation applies to using an `ImmutableContext`. The evaluation context includes user or session information used to determine which flag variations should be returned. Set this before evaluating flags to help ensure proper targeting.

```kotlin
import dev.openfeature.kotlin.sdk.ImmutableContext
import dev.openfeature.kotlin.sdk.Value

OpenFeatureAPI.setEvaluationContext(
    ImmutableContext(
        targetingKey = "user-123",
        attributes = mapOf(
            "email" to Value.String("user@example.com"),
            "tier" to Value.String("premium")
        )
    )
)
```

{% alert level="info" %}
All attribute values must use a `Value.String()` wrapper. The targeting key should be consistent for the same user to help ensure consistent flag evaluation across sessions. For anonymous users, use a persistent UUID stored, for example, in `SharedPreferences`.
{% /alert %}

## Evaluate flags{% #evaluate-flags %}

After setting up your provider and evaluation context, you can read flag values throughout your app. Flag evaluation is *local and instantaneous*—the SDK uses locally cached data, so no network requests occur when evaluating flags. This makes evaluations safe to perform on the main thread.

Each flag is identified by a *key* (a unique string) and can be evaluated with a typed method that returns a value of the expected type. If the flag doesn't exist or cannot be evaluated, the SDK returns the provided default value.

First, get an OpenFeature client:

```kotlin
import dev.openfeature.kotlin.sdk.OpenFeatureAPI

val client = OpenFeatureAPI.getClient()
```

### Boolean flags{% #boolean-flags %}

Boolean flags represent on/off or true/false conditions:

```kotlin
val isNewCheckoutEnabled = client.getBooleanValue(
    key = "checkout.new",
    defaultValue = false
)

if (isNewCheckoutEnabled) {
    showNewCheckoutFlow()
} else {
    showLegacyCheckout()
}
```

### String flags{% #string-flags %}

String flags select between multiple variants or configuration strings:

```kotlin
val theme = client.getStringValue(
    key = "ui.theme",
    defaultValue = "light"
)

when (theme) {
    "light" -> setLightTheme()
    "dark" -> setDarkTheme()
    else -> setLightTheme()
}
```

### Integer and double flags{% #integer-and-double-flags %}

Numeric flags are appropriate when a feature depends on a numeric parameter such as a limit, percentage, or multiplier:

```kotlin
val maxItems = client.getIntegerValue(
    key = "cart.items.max",
    defaultValue = 20
)

val priceMultiplier = client.getDoubleValue(
    key = "pricing.multiplier",
    defaultValue = 1.0
)
```

### Structured flags{% #structured-flags %}

Structured flags are useful for remote configuration scenarios where multiple properties need to be provided together as JSON-like data:

```kotlin
import dev.openfeature.kotlin.sdk.Value

val config = client.getObjectValue(
    key = "ui.config",
    defaultValue = Value.Structure(mapOf(
        "color" to Value.String("#00A3FF"),
        "fontSize" to Value.Integer(14)
    ))
)

// Access nested values
val color = config.asStructure()?.get("color")?.asString()
val fontSize = config.asStructure()?.get("fontSize")?.asInteger()
```

### Flag evaluation details{% #flag-evaluation-details %}

When you need more than the flag value, you can get detailed evaluation metadata including the evaluated value, variant name, reason, and any error codes:

```kotlin
val details = client.getStringDetails(
    key = "paywall.layout",
    defaultValue = "control"
)

print(details.value)      // Evaluated value (for example: "A", "B", or "control")
print(details.variant)    // Variant name, if applicable
print(details.reason)     // Reason for this value (for example: "TARGETING_MATCH" or "DEFAULT")
print(details.errorCode)  // Error code, if any
```

Similar detail methods exist for other types: `getBooleanDetails()`, `getIntegerDetails()`, `getDoubleDetails()`, and `getObjectDetails()`.

Flag details help you debug evaluation behavior and understand why a user received a given value.

## Advanced configuration{% #advanced-configuration %}

### Global configuration{% #global-configuration %}

The `Flags.enable()` API accepts optional configuration with the options listed below. These settings apply globally to all providers:

```kotlin
val config = FlagsConfiguration.Builder()
    // configure options here
    .build()

Flags.enable(config)
```

{% dl %}

{% dt %}
`trackExposures()`
{% /dt %}

{% dd %}
When `true` (default), the SDK automatically records an *exposure event* when a flag is evaluated. These events contain metadata about which flag was accessed, which variant was served, and under what context. They are sent to Datadog so you can later analyze feature adoption. If you only need local evaluation without telemetry, you can disable it with: `trackExposures(false)`.
{% /dd %}

{% dt %}
`rumIntegrationEnabled()`
{% /dt %}

{% dd %}
When `true` (default), flag evaluations are tracked in RUM, which enables correlating them with user sessions. This enables analytics such as *"Do users in variant B experience more errors?"*. If your app does not use RUM, this flag has no effect and can be safely left at its default value. Use `rumIntegrationEnabled(false)` to disable RUM integration.
{% /dd %}

{% dt %}
`gracefulModeEnabled()`
{% /dt %}

{% dd %}
Controls how the SDK handles incorrect use of the API—for example, creating a client before calling `Flags.enable()`, creating a duplicate client with the same name, or retrieving a client that hasn't been created yet.
The exact behavior of Graceful Mode depends on your build configuration:

- **Release builds**: The SDK always enforces Graceful Mode: any misuse is only logged internally if `Datadog.setVerbosity()` is configured.
- **Debug builds** with `gracefulModeEnabled = true` (default): The SDK always logs warnings to the console.
- **Debug builds** with `gracefulModeEnabled = false`: The SDK raises `IllegalStateException` for incorrect API usage, enforcing a fail-fast approach that helps detect configuration mistakes early.

You can adjust `gracefulModeEnabled()` depending on your development or QA phase.
{% /dd %}

{% /dl %}

### Per-provider configuration{% #per-provider-configuration %}

You can configure individual providers with custom endpoints before creating them:

```kotlin
val provider = FlagsClient.Builder()
    .useCustomFlagEndpoint("https://your-proxy.example.com/flags")
    .useCustomExposureEndpoint("https://your-proxy.example.com/exposure")
    .build()
    .asOpenFeatureProvider()

OpenFeatureAPI.setProviderAndWait(provider)
```

## Direct FlagsClient integration (advanced){% #direct-flagsclient-integration-advanced %}

For most applications, the OpenFeature API described above is the recommended approach. However, you can use the Datadog `FlagsClient` directly if you have specific requirements that the OpenFeature abstraction doesn't support.

**Use FlagsClient directly only if you:**

- Require **multiple independent evaluation contexts** in the same app (for example, different contexts for different users in a multi-user app)
- Want to work with **native Kotlin types** directly (`JSONObject` instead of `Value.Structure`)
- Need **fine-grained control** over client lifecycle and configuration per instance

### Installation (FlagsClient){% #installation-flagsclient %}

If you only need the direct API, you can omit the OpenFeature dependency:

In the `build.gradle` file:

```groovy
dependencies {
    implementation "com.datadoghq:dd-sdk-android-flags:<latest-version>"

    // Recommended: RUM integration drives analysis and enriches RUM session data
    implementation "com.datadoghq:dd-sdk-android-rum:<latest-version>"
}
```

### Create and retrieve a client (FlagsClient){% #create-and-retrieve-a-client-flagsclient %}

Create a client once, typically during app startup:

```kotlin
FlagsClient.Builder().build() // Creates the default client
```

Retrieve the same client anywhere in your app:

```kotlin
val flagsClient = FlagsClient.get() // Retrieves the "default" client
```

You can also create and retrieve multiple clients by providing the `name` parameter:

```kotlin
FlagsClient.Builder("checkout").build()
val flagsClient = FlagsClient.get("checkout")
```

{% alert level="info" %}
If a client with the given name already exists, the existing instance is reused.
{% /alert %}

### Set the evaluation context (FlagsClient){% #set-the-evaluation-context-flagsclient %}

```kotlin
flagsClient.setEvaluationContext(
    EvaluationContext(
        targetingKey = "user-123",
        attributes = mapOf(
            "email" to "user@example.com",
            "tier" to "premium"
        )
    )
)
```

This method fetches flag assignments from the server asynchronously in the background. The operation is non-blocking and thread-safe. Flag updates are available for subsequent evaluations after the background operation completes.

### Evaluate flags (FlagsClient){% #evaluate-flags-flagsclient %}

{% collapsible-section %}
#### Boolean flags

```kotlin
val isNewCheckoutEnabled = flagsClient.resolveBooleanValue(
    flagKey = "checkout.new",
    defaultValue = false
)
```

{% /collapsible-section %}

{% collapsible-section %}
#### String flags

```kotlin
val theme = flagsClient.resolveStringValue(
    flagKey = "ui.theme",
    defaultValue = "light"
)
```

{% /collapsible-section %}

{% collapsible-section %}
#### Integer and double flags

```kotlin
val maxItems = flagsClient.resolveIntValue(
    flagKey = "cart.items.max",
    defaultValue = 20
)

val priceMultiplier = flagsClient.resolveDoubleValue(
    flagKey = "pricing.multiplier",
    defaultValue = 1.0
)
```

{% /collapsible-section %}

{% collapsible-section %}
#### Structured flags

```kotlin
import org.json.JSONObject

val config = flagsClient.resolveStructureValue(
    flagKey = "ui.config",
    defaultValue = JSONObject().apply {
        put("color", "#00A3FF")
        put("fontSize", 14)
    }
)
```

{% /collapsible-section %}

{% collapsible-section %}
#### Flag evaluation details

```kotlin
val details = flagsClient.resolve(
    flagKey = "paywall.layout",
    defaultValue = "control"
)

print(details.value)      // Evaluated value (for example: "A", "B", or "control")
print(details.variant)    // Variant name, if applicable
print(details.reason)     // Description of why this value was chosen
print(details.errorCode)  // The error that occurred during evaluation, if any
```

{% /collapsible-section %}

### API comparison{% #api-comparison %}

This table highlights key differences between the OpenFeature and `FlagsClient` APIs to help you choose the integration that fits your requirements.

| Feature                | **OpenFeature**              | **FlagsClient**              |
| ---------------------- | ---------------------------- | ---------------------------- |
| **API Standard**       | OpenFeature (vendor-neutral) | Datadog-specific             |
| **Evaluation Context** | Global/static                | Per client instance          |
| **Structured Flags**   | `Value.Structure`            | `JSONObject`                 |
| **Type Safety**        | OpenFeature `Value` types    | Kotlin-native types          |
| **Vendor Lock-in**     | Low (vendor-neutral)         | Higher (Datadog-specific)    |
| **State Management**   | Flow-based observation       | Manual listener registration |

## Further reading{% #further-reading %}

- [Client-Side Feature Flags](https://docs.datadoghq.com/feature_flags/client.md)
- [OpenFeature Kotlin SDK](https://openfeature.dev/docs/reference/technologies/client/kotlin/)
- [Android and Android TV Monitoring](https://docs.datadoghq.com/real_user_monitoring/android.md)
