Skip to content
Open
50 changes: 33 additions & 17 deletions example-new-architecture/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import {
DdFlags,
PropagatorType,
} from '@datadog/mobile-react-native';
import {DatadogOpenFeatureProvider} from '@datadog/mobile-react-native-openfeature';
import {
OpenFeature,
OpenFeatureProvider,
useBooleanFlagDetails,
useObjectFlagDetails,
} from '@openfeature/react-sdk';
import {setFlagsProvider} from './flags/flagsProvider';
import {FlagsSourceToggle} from './flags/FlagsSourceToggle';
import React, {Suspense} from 'react';
import type {PropsWithChildren} from 'react';
import {
Expand Down Expand Up @@ -75,9 +76,10 @@ import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials';
// Enable Datadog Flags feature.
await DdFlags.enable();

// Set the provider with OpenFeature.
const provider = new DatadogOpenFeatureProvider();
OpenFeature.setProvider(provider);
// Set the flags provider. This app defaults to the offline provider (a bundled
// ConfigurationWire, no network); the "Flags source" switch flips to the online provider
// (CDN) at runtime.
await setFlagsProvider('offline');

// Datadog SDK usage examples.
await DdRum.startView('main', 'Main');
Expand All @@ -93,18 +95,9 @@ import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials';
})();

function AppWithProviders() {
React.useEffect(() => {
const user = {
id: 'user-123',
favoriteFruit: 'apple',
};

OpenFeature.setContext({
targetingKey: user.id,
favoriteFruit: user.favoriteFruit,
});
}, []);

// No OpenFeature.setContext here on purpose: the offline precomputed configuration is a
// single-subject snapshot served against the context it was computed for (see the wire's
// embedded context in flags/). A runtime context would simply be ignored (with a warning).
return (
<Suspense
fallback={
Expand All @@ -123,6 +116,8 @@ function App(): React.JSX.Element {
const greetingFlag = useObjectFlagDetails('rn-sdk-test-json-flag', {
greeting: 'Default greeting',
});
// The boolean flag carried by the bundled offline configuration.
const offlineFlag = useBooleanFlagDetails('rn-sdk-test-boolean-flag', false);

const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
Expand All @@ -138,6 +133,27 @@ function App(): React.JSX.Element {
<ScrollView contentInsetAdjustmentBehavior="automatic" style={backgroundStyle}>
<Header />

<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{color: isDarkMode ? Colors.white : Colors.black},
]}>
Offline Feature Flag
</Text>
<Text style={styles.sectionDescription}>
{offlineFlag.value
? 'Greetings from the offline Feature Flags!'
: 'Welcome!'}
</Text>
<Text style={styles.sectionDescription}>
`{offlineFlag.flagKey}` ={' '}
<Text style={styles.highlight}>{String(offlineFlag.value)}</Text>,
reason <Text style={styles.highlight}>{offlineFlag.reason}</Text>.
</Text>
<FlagsSourceToggle initialSource="offline" />
</View>

<View style={{backgroundColor: isDarkMode ? Colors.black : Colors.white}}>
<Section title={greetingFlag.value.greeting}>
The title of this section is based on the{' '}
Expand Down
63 changes: 63 additions & 0 deletions example-new-architecture/flags/FlagsSourceToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, {useState} from 'react';
import {
View,
Text,
Switch,
ActivityIndicator,
StyleSheet,
} from 'react-native';

import {setFlagsProvider} from './flagsProvider';
import type {FlagsSource} from './flagsProvider';

/**
* A runtime switch between the offline (bundled, no network) and online (CDN) flags
* providers. Toggling re-sets the OpenFeature provider, which re-renders any `<FeatureFlag>`.
*/
export const FlagsSourceToggle = ({
initialSource = 'offline',
}: {
initialSource?: FlagsSource;
}) => {
const [offline, setOffline] = useState(initialSource === 'offline');
const [busy, setBusy] = useState(false);

const onToggle = async (nextOffline: boolean) => {
setBusy(true);
try {
await setFlagsProvider(nextOffline ? 'offline' : 'online');
setOffline(nextOffline);
} finally {
setBusy(false);
}
};

return (
<View style={styles.container}>
<Text style={styles.label}>
Flags source: {offline ? 'offline (bundled)' : 'online (CDN)'}
</Text>
<Switch
accessibilityLabel="flags_source_toggle"
value={offline}
onValueChange={onToggle}
disabled={busy}
/>
{busy ? <ActivityIndicator style={styles.spinner} /> : null}
</View>
);
};

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 8,
},
label: {
marginRight: 10,
},
spinner: {
marginLeft: 10,
},
});
32 changes: 32 additions & 0 deletions example-new-architecture/flags/flagsProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
DatadogOpenFeatureProvider,
DatadogOfflineOpenFeatureProvider,
configurationFromString,
} from '@datadog/mobile-react-native-openfeature';
import {OpenFeature} from '@openfeature/react-sdk';

import {buildSampleWire} from './sampleOfflineConfiguration';

export type FlagsSource = 'online' | 'offline';

/**
* Select which OpenFeature provider backs flag evaluations, and (re)set it at runtime.
*
* - `offline`: loads a bundled `ConfigurationWire` into `DatadogOfflineOpenFeatureProvider`
* **before** setting it, so flags resolve immediately with no network request.
* - `online`: the standard `DatadogOpenFeatureProvider`, which fetches assignments from the CDN.
*
* `DdFlags.enable()` must have been called once before this (it enables the native feature).
*/
export const setFlagsProvider = async (source: FlagsSource): Promise<void> => {
if (source === 'offline') {
const provider = new DatadogOfflineOpenFeatureProvider();
provider.setConfiguration(
configurationFromString(buildSampleWire()),
);
await OpenFeature.setProviderAndWait(provider);
return;
}

await OpenFeature.setProviderAndWait(new DatadogOpenFeatureProvider());
};
50 changes: 50 additions & 0 deletions example-new-architecture/flags/sampleOfflineConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// The flag key shared with the online example, so the UI is comparable across providers.
export const OFFLINE_FLAG_KEY = 'rn-sdk-test-boolean-flag';

export type OfflineWireContext = {targetingKey?: string} & Record<
string,
string | number | boolean
>;

// The evaluation context the bundled configuration is precomputed for. Because the wire
// carries its own context, the app does not need to call `OpenFeature.setContext` for the
// offline flow.
export const DEFAULT_OFFLINE_CONTEXT: OfflineWireContext = {
targetingKey: 'example-offline-user',
};

/**
* Build a bundled `ConfigurationWire` v1 string for the offline example.
*
* Mirrors the shape the Datadog Flags CDN returns, but is bundled with the app so the demo
* is fully offline — it never hits the network. Flip `variationValue` to `false` to confirm
* the flag's fallback renders.
*/
export const buildSampleWire = (
context: OfflineWireContext = DEFAULT_OFFLINE_CONTEXT,
variationValue = true,
): string =>
JSON.stringify({
version: 1,
precomputed: {
context,
response: JSON.stringify({
data: {
attributes: {
obfuscated: false,
flags: {
[OFFLINE_FLAG_KEY]: {
variationType: 'boolean',
variationValue,
variationKey: String(variationValue),
allocationKey: 'offline-example-alloc',
reason: 'STATIC',
doLog: true,
extraLogging: {},
},
},
},
},
}),
},
});
11 changes: 6 additions & 5 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import style from './screens/styles';
import { navigationRef } from './NavigationRoot';
import { DdRumReactNavigationTracking, NavigationTrackingOptions, ParamsTrackingPredicate, ViewNamePredicate, ViewTrackingPredicate } from '@datadog/mobile-react-navigation';
import { DatadogProvider, TrackingConsent, DdFlags } from '@datadog/mobile-react-native'
import { DatadogOpenFeatureProvider } from '@datadog/mobile-react-native-openfeature';
import { OpenFeature, OpenFeatureProvider } from '@openfeature/react-sdk';
import { OpenFeatureProvider } from '@openfeature/react-sdk';
import { Route } from "@react-navigation/native";
import { NestedNavigator } from './screens/NestedNavigator/NestedNavigator';
import { getDatadogConfig, onDatadogInitialization } from './ddUtils';
import { setFlagsProvider } from './flags/flagsProvider';

const Tab = createBottomTabNavigator();

Expand Down Expand Up @@ -74,9 +74,10 @@ const handleDatadogInitialization = async () => {
// Enable Datadog Flags feature.
await DdFlags.enable();

// Set the provider with OpenFeature.
const provider = new DatadogOpenFeatureProvider();
OpenFeature.setProvider(provider);
// Set the flags provider. This example defaults to the offline provider (a bundled
// ConfigurationWire, no network); the "Flags source" switch on the Home screen flips to
// the online provider (CDN) at runtime.
await setFlagsProvider('offline');
}

export default function App() {
Expand Down
57 changes: 57 additions & 0 deletions example/src/components/FlagsSourceToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { useState } from 'react';
import { View, Text, Switch, ActivityIndicator, StyleSheet } from 'react-native';

import { setFlagsProvider } from '../flags/flagsProvider';
import type { FlagsSource } from '../flags/flagsProvider';

/**
* A runtime switch between the offline (bundled, no network) and online (CDN) flags
* providers. Toggling re-sets the OpenFeature provider, which re-renders any `<FeatureFlag>`.
*/
export const FlagsSourceToggle = ({
initialSource = 'offline'
}: {
initialSource?: FlagsSource;
}) => {
const [offline, setOffline] = useState(initialSource === 'offline');
const [busy, setBusy] = useState(false);

const onToggle = async (nextOffline: boolean) => {
setBusy(true);
try {
await setFlagsProvider(nextOffline ? 'offline' : 'online');
setOffline(nextOffline);
} finally {
setBusy(false);
}
};

return (
<View style={styles.container}>
<Text style={styles.label}>
Flags source: {offline ? 'offline (bundled)' : 'online (CDN)'}
</Text>
<Switch
accessibilityLabel="flags_source_toggle"
value={offline}
onValueChange={onToggle}
disabled={busy}
/>
{busy ? <ActivityIndicator style={styles.spinner} /> : null}
</View>
);
};

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12
},
label: {
marginRight: 10
},
spinner: {
marginLeft: 10
}
});
36 changes: 36 additions & 0 deletions example/src/flags/flagsProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
DatadogOpenFeatureProvider,
DatadogOfflineOpenFeatureProvider,
configurationFromString
} from '@datadog/mobile-react-native-openfeature';
import { OpenFeature } from '@openfeature/react-sdk';

import { buildSampleWire } from './sampleOfflineConfiguration';
import type { OfflineWireContext } from './sampleOfflineConfiguration';

export type FlagsSource = 'online' | 'offline';

/**
* Select which OpenFeature provider backs flag evaluations, and (re)set it at runtime.
*
* - `offline`: loads a bundled `ConfigurationWire` into `DatadogOfflineOpenFeatureProvider`
* **before** setting it, so flags resolve immediately with no network request.
* - `online`: the standard `DatadogOpenFeatureProvider`, which fetches assignments from the CDN.
*
* `DdFlags.enable()` must have been called once before this (it enables the native feature).
*/
export const setFlagsProvider = async (
source: FlagsSource,
offlineContext?: OfflineWireContext
): Promise<void> => {
if (source === 'offline') {
const provider = new DatadogOfflineOpenFeatureProvider();
provider.setConfiguration(
configurationFromString(buildSampleWire(offlineContext))
);
await OpenFeature.setProviderAndWait(provider);
return;
}

await OpenFeature.setProviderAndWait(new DatadogOpenFeatureProvider());
};
Loading