Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions hugo/content/en/feature_flags/client/android.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ Similar detail methods exist for other types: `getBooleanDetails()`, `getInteger

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

With `dd-sdk-android-flags` 3.15.0 and later, a successful evaluation from flags restored from persistent storage reports `CACHED`. After a network response replaces those flags, successful evaluations use the reasons supplied by that response. Check the error field as well: a missing flag or type mismatch returns an error and the default value, rather than a successful cached evaluation.

`CACHED` describes where the evaluated assignment came from. It does not mean the configuration is fresh, matches a newly requested evaluation context, or that an OpenFeature client has finished initialisation.

## Observe provider events

<div class="alert alert-info">Provider event observation is available in <code>dd-sdk-android-flags-openfeature</code> 3.6.0 and later. Use the same version for <code>dd-sdk-android-flags</code>.</div>
Expand Down
6 changes: 6 additions & 0 deletions hugo/content/en/feature_flags/client/flutter.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,12 @@ print(details.error?.code);

`FlagDetails.error` is set when the SDK returns the default value because the provider is not ready, the flag is not found, or the assignment value does not match the typed evaluation method. Successful details include the evaluated value plus assignment metadata, such as `variant` and `reason`, when Datadog returned it.

<!-- Publication gate: confirm the first SDK release supporting CACHED before merging this addition. -->

In SDK versions that support the `CACHED` evaluation reason, a successful evaluation from flags restored from persistent storage reports `CACHED`. After a network response replaces those flags, successful evaluations use the reasons supplied by that response.

`CACHED` describes where the evaluated assignment came from. It does not mean the configuration is fresh or matches a newly requested evaluation context.

## Advanced configuration

`DatadogFlagsConfiguration` controls SDK behavior:
Expand Down
6 changes: 6 additions & 0 deletions hugo/content/en/feature_flags/client/ios.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,12 @@ print(details.error) // The error that occurred during evaluation, if any

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

<!-- Publication gate: confirm the first SDK release supporting CACHED before merging this addition. -->

In SDK versions that support the `CACHED` evaluation reason, a successful evaluation from flags restored from persistent storage reports `CACHED`. After a network response replaces those flags, successful evaluations use the reasons supplied by that response. Check the error field as well: a missing flag or type mismatch returns an error and the default value, rather than a successful cached evaluation.

`CACHED` describes where the evaluated assignment came from. It does not mean the configuration is fresh, matches a newly requested evaluation context, or that an OpenFeature client has finished initialisation.

## Observe state changes

<div class="alert alert-info">State observation with <code>FlagsClient.state</code> is available in <code>dd-sdk-ios</code> 3.11.0 and later.</div>
Expand Down
177 changes: 177 additions & 0 deletions hugo/content/en/feature_flags/guide/first_flags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
---
title: Use First Flags in Client Applications
description: Read available feature flags during startup with First Flags callbacks in Android, iOS, and Flutter applications.
draft: true
private: true
further_reading:
- link: "/feature_flags/client/android/"
tag: "Documentation"
text: "Android Feature Flags"
- link: "/feature_flags/client/ios/"
tag: "Documentation"
text: "iOS Feature Flags"
- link: "/feature_flags/client/flutter/"
tag: "Documentation"
text: "Dart and Flutter Feature Flags"
---

<div class="alert alert-warning">First Flags is available on Android in <code>dd-sdk-android-flags</code> 3.15.0 and later. The Apple and Dart/Flutter examples describe APIs pending release confirmation; this guide remains a draft until their supporting versions are verified.</div>

## Overview

Use First Flags to react when a client first installs flags from its local cache or a network response. For example, read a Boolean flag from disk during startup without waiting for the first network request to finish.

First Flags reports that a configuration was accepted. The client can replace or clear that configuration before the callback runs. It does not indicate that the data is fresh or targeted to the user signing in. Choose whether previously cached values are appropriate for the feature, and keep a default value for reads that fail.

Before using the examples, complete the SDK initialisation and flags setup for [Android][1], [iOS][2], or [Dart and Flutter][3]. Use the same client for registration and evaluation. Replace `my-flag-key` with a Boolean flag key from the configured environment.

## Understand the callback

Register with `client.onFirstFlags(...)`. For a configured client, each registration receives one event for the first accepted cache or network installation:

- A valid configuration with no flags triggers the callback with an empty key list. Missing or invalid cached data and failed requests do not trigger it. If no configuration is ever accepted, the callback remains pending indefinitely.
- `type` identifies `CONFIGURATION_CHANGED`. `flagsChanged` contains all keys from that first installation. Although the field is optional in the event API, a First Flags event supplies a list, which can be empty.
- The keys describe the installed flags, not every flag in Datadog or flags the application has evaluated. Do not evaluate every key to collect an inventory.
- The client retains information about the first installation. Registering later delivers an event describing that installation, including after later updates. This delivery does not fetch or reload flags.
- The event contains keys, not values. Evaluations in the callback read the client's data at evaluation time, which can differ from the first installation.

First Flags is a one-time notification for each registration, not a subscription to subsequent configuration changes. A context update does not produce another callback for the same registration.

**Apple fallback exception:** If the SDK returns a fallback client, each registration immediately receives a `.configurationChanged` event with `flagsChanged: []` on the registering thread. This does not represent an accepted configuration or successful initialisation. The client remains in its error state, evaluations return defaults with `providerNotReady`, and setting context fails. The returned subscription has no effect.

## Register and evaluate

The examples register before starting context initialisation so the callback can observe an early cache installation. Retained replay also handles an installation that finishes before registration.

### Android

After initialising Datadog and enabling `Flags`, create a client and register a listener:

```kotlin
import android.util.Log
import com.datadog.android.flags.FlagsClient
import com.datadog.android.flags.model.EvaluationContext

val client = FlagsClient.Builder().build()
val subscription = client.onFirstFlags { event ->
Log.i("FeatureFlags", "First flag keys: ${event.flagsChanged}")
val details = client.resolve("my-flag-key", false)
Log.i("FeatureFlags", "Value: ${details.value}, reason: ${details.reason}, error: ${details.errorCode}")
}
client.setEvaluationContext(EvaluationContext(targetingKey = "user-123"))
```

Retain `subscription` with the component that owns the listener. To stop pending delivery when the component is destroyed, call:

```kotlin
subscription.unsubscribe()
```

If your application uses OpenFeature, retain this native client and pass its `asOpenFeatureProvider()` adapter to OpenFeature. Set context through OpenFeature for that integration; do not also start a separate native context request for the same operation.

### Apple platforms

After initialising Datadog and enabling `Flags`, use a model that owns the client and its subscription. Dispatch work that updates application state to the main actor.

```swift
import DatadogFlags

@MainActor
final class FirstFlagsModel {
private let client = FlagsClient.create()
private var subscription: (any FlagsSubscription)?

func start() {
subscription = client.onFirstFlags { @Sendable [weak self] event in
let keys = event.flagsChanged
Task { @MainActor in
guard let self else { return }
print("First flag keys: \(keys ?? [])")
let details = self.client.getBooleanDetails(
key: "my-flag-key",
defaultValue: false
)
print("Value: \(details.value), reason: \(details.reason ?? "unavailable"), error: \(String(describing: details.error))")
}
}
client.setEvaluationContext(FlagsEvaluationContext(targetingKey: "user-123"))
}

func stop() {
subscription?.cancel()
subscription = nil
}
}
```

Retain the model for the component's lifetime, call `start()` once, and call `stop()` during cleanup. Cancellation stops pending SDK delivery; it does not cancel application work already scheduled in a `Task`.

If you create a `DatadogProvider` for OpenFeature, it creates a native client. Retrieve that registered client with `FlagsClient.shared(named:in:)`, using the same name and Datadog core as the provider. Register on that client instead of creating another one. Let OpenFeature manage context updates for that integration.

### Flutter

After initialising the Datadog Flutter SDK with `DatadogFlagsPluginConfiguration`, retrieve the existing client. Register before awaiting its initialisation:

```dart
import 'package:datadog_flags_flutter/datadog_flags_flutter.dart';
import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart';

void Function()? unregisterFirstFlags;

Future<void> startFlags() async {
final client = DatadogSdk.instance.flags?.sharedClient();
if (client == null) return;

unregisterFirstFlags = client.onFirstFlags((event) {
print('First flag keys: ${event.flagsChanged}');
final details = client.getBooleanDetails(
key: 'my-flag-key',
defaultValue: false,
);
print('Value: ${details.value}, reason: ${details.reason}, error: ${details.error}');
});
await client.initialize(
const FlagsEvaluationContext(targetingKey: 'user-123'),
);
}

void stopFlags() {
unregisterFirstFlags?.call();
unregisterFirstFlags = null;
}
```

Call `startFlags()` once from the existing initialisation flow and handle initialisation failures there. Call `stopFlags()` from the owning component's cleanup. A Flutter widget can store the unregister function in its `State` and invoke it from `dispose()`.

For standalone Dart, use the same `onFirstFlags` method on the client created with the [Dart setup][3]. The Flutter example uses the plugin's `sharedClient()` API; do not construct its implementation class directly.

## Account for delivery timing

| Platform | Registration when the first event is available | Registration before the first event is available | Cleanup |
|---|---|---|---|
| Android | Immediate on the registering thread, before `onFirstFlags` returns | Background delivery | `subscription.unsubscribe()` |
| iOS configured client | Synchronous on the registering thread, before `onFirstFlags` returns | Asynchronously on a background executor, after installation bookkeeping | `subscription.cancel()` |
| Dart and Flutter | A microtask after registration | A microtask after the event is available and client binding completes | Call the returned unregister function |

Android and iOS callbacks are not guaranteed to run on the UI thread. Dart and Flutter callbacks run in the zone where the listener was registered, including replay. Keep callbacks short and dispatch UI work as needed.

Cancel a registration to release captured references if flags never become available. Cancellation is idempotent and affects only that registration; it does not stop cache loading, network requests, or other listeners. Delivery already claimed by the SDK can still run. Discarding the subscription or unregister function does not cancel it.

## Interpret an early evaluation

With an SDK release that supports the `CACHED` evaluation reason, a successful read from flags restored from disk reports `CACHED`. A network installation uses its own evaluation reason. The callback event does not report which source won. Evaluation details describe the flag value read at evaluation time, which can come from a later installation.

`CACHED` does not mean the flags match a newly requested user context or reflect the latest server configuration. An empty installation can trigger First Flags while a particular flag still returns its default because the key is absent. Inspect the error field as well as the value and reason.

A native client can have flags before OpenFeature finishes initialisation. First Flags does not change OpenFeature readiness or bypass an OpenFeature SDK's evaluation gate. Use the native client for the early read shown here. Native reads use Datadog's configured evaluation tracking, but do not run OpenFeature hooks. A later call through OpenFeature is a separate evaluation.

## Verify delivery

1. Register the callback and start a client that has no stored flags. After a successful network response, check that the callback logs the installed keys.
2. Restart with stored flags and delay the network response. If the cache is accepted first, check that the callback runs and a successful cached read reports `CACHED` on a supporting SDK version.
3. Register another listener after installation. Check that it receives the retained first keys with the platform-specific timing above. Its evaluation can use newer data.
4. Cancel a pending registration and verify that it receives no callback unless delivery was already claimed.

[1]: /feature_flags/client/android/
[2]: /feature_flags/client/ios/
[3]: /feature_flags/client/flutter/
Loading