Skip to content
Open
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
156 changes: 82 additions & 74 deletions example-new-architecture/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import {
RumConfiguration,
DdFlags,
} from '@datadog/mobile-react-native';
import React from 'react';
import {DatadogProvider} from '@datadog/openfeature-react-native';
import {
OpenFeature,
OpenFeatureProvider,
useObjectFlagDetails,
} from '@openfeature/react-sdk';
import React, {Suspense} from 'react';
import type {PropsWithChildren} from 'react';
import {
ActivityIndicator,
Expand All @@ -35,119 +41,91 @@ import {
import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials';

(async () => {
const config = new CoreConfiguration(
CLIENT_TOKEN,
ENVIRONMENT,
);
const config = new CoreConfiguration(CLIENT_TOKEN, ENVIRONMENT);
config.verbosity = SdkVerbosity.DEBUG;
config.uploadFrequency = UploadFrequency.FREQUENT;
config.batchSize = BatchSize.SMALL;

// Enable RUM.
config.rumConfiguration = new RumConfiguration(
APPLICATION_ID,
true,
true,
true
)
true,
);
config.rumConfiguration.sessionSampleRate = 100;
config.rumConfiguration.telemetrySampleRate = 100;

// Initialize the Datadog SDK.
await DdSdkReactNative.initialize(config);

// Enable Flags.
await DdFlags.enable();

// Usage examples.
await DdRum.startView('main', 'Main');
setTimeout(async () => {
await DdRum.addTiming('one_second');
}, 1000);
await DdRum.addAction(RumActionType.CUSTOM, 'custom action');

await DdLogs.info('info log');

const spanId = await DdTrace.startSpan('test span');
await DdTrace.finishSpan(spanId);
})();

type SectionProps = PropsWithChildren<{
title: string;
}>;
function AppWithProviders() {
React.useEffect(() => {
const userId = 'user-123'

const evaluationContext = {
targetingKey: userId,
favoriteFruit: 'apple'
}

const provider = new DatadogProvider();
OpenFeature.setProvider(provider, evaluationContext);
}, [])

function Section({children, title}: SectionProps): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
{children}
</Text>
</View>
<Suspense
fallback={
<SafeAreaView style={{height: '100%', justifyContent: 'center'}}>
<ActivityIndicator />
</SafeAreaView>
}>
<OpenFeatureProvider suspendUntilReady>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧑‍🍳

<App />
</OpenFeatureProvider>
</Suspense>
);
}

function App(): React.JSX.Element {
const [isInitialized, setIsInitialized] = React.useState(false);

React.useEffect(() => {
(async () => {
// This is a blocking async app initialization effect.
// It simulates the way most React Native applications are initialized.
await DdFlags.enable();
const client = DdFlags.getClient();

const userId = 'test-user-1';
const userAttributes = {
country: 'US',
};

await client.setEvaluationContext({targetingKey: userId, attributes: userAttributes});

setIsInitialized(true);
})().catch(console.error);
}, []);
const greetingFlag = useObjectFlagDetails('rn-sdk-test-json-flag', {greeting: 'Default greeting'});

const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};

if (!isInitialized) {
return (
<SafeAreaView style={{height: '100%', justifyContent: 'center'}}>
<ActivityIndicator />
</SafeAreaView>
);
}

// TODO: [FFL-908] Use OpenFeature SDK instead of a manual client call.
const testFlagKey = 'rn-sdk-test-json-flag';
const testFlag = DdFlags.getClient().getObjectValue(testFlagKey, {greeting: "Default greeting"}); // https://app.datadoghq.com/feature-flags/bcf75cd6-96d8-4182-8871-0b66ad76127a?environmentId=d114cd9a-79ed-4c56-bcf3-bcac9293653b

return (
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>
<ScrollView contentInsetAdjustmentBehavior="automatic" style={backgroundStyle}>
<Header />
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
<Section title="Feature Flags">
Flag value for <Text style={styles.highlight}>{testFlagKey}</Text> is{'\n'}
<Text style={styles.highlight}>{JSON.stringify(testFlag)}</Text>

<View style={{backgroundColor: isDarkMode ? Colors.black : Colors.white}}>
<Section title={greetingFlag.value.greeting}>
The title of this section is based on the <Text style={styles.highlight}>{greetingFlag.flagKey}</Text> feature flag.{'\n\n'}

If it's different from "Default greeting", then it is coming from the feature flag evaluation.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍
consider adding the details.evaluation_reason here as well.

</Section>

<Section title="Step One">
Edit <Text style={styles.highlight}>App.tsx</Text> to change this
screen and then come back to see your edits.
Expand All @@ -168,6 +146,36 @@ function App(): React.JSX.Element {
);
}

type SectionProps = PropsWithChildren<{
title: string;
}>;

function Section({children, title}: SectionProps): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
{children}
</Text>
</View>
);
}

const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
Expand All @@ -187,4 +195,4 @@ const styles = StyleSheet.create({
},
});

export default App;
export default AppWithProviders;
2 changes: 2 additions & 0 deletions example-new-architecture/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
},
"dependencies": {
"@datadog/mobile-react-native": "workspace:packages/core",
"@datadog/openfeature-react-native": "workspace:packages/react-native-openfeature-provider",
"@openfeature/react-sdk": "^1.1.0",
"react": "18.3.1",
"react-native": "0.76.9"
},
Expand Down
72 changes: 8 additions & 64 deletions packages/core/src/flags/DdFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,89 +10,33 @@ import type { DdNativeFlagsType } from '../nativeModulesTypes';
import { getGlobalInstance } from '../utils/singletonUtils';

import { FlagsClient } from './FlagsClient';
import type { DdFlagsType, DdFlagsConfiguration } from './types';
import type { DdFlagsType, FlagsConfiguration } from './types';

const FLAGS_MODULE = 'com.datadog.reactnative.flags';

/**
* Implementation class for {@link DdFlagsType}. Please see the interface for documentation.
*/
class DdFlagsWrapper implements DdFlagsType {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
private nativeFlags: DdNativeFlagsType = require('../specs/NativeDdFlags')
.default;

private isFeatureEnabled = false;

private clients: Record<string, FlagsClient> = {};

/**
* Enables the Datadog Flags feature in your application.
*
* Call this method after initializing the Datadog SDK to enable feature flag evaluation.
* This method must be called before creating any `FlagsClient` instances via `DdFlags.getClient()`.
*
* @example
* ```ts
* import { DdSdkReactNativeConfiguration, DdSdkReactNative, DdFlags } from '@datadog/mobile-react-native';
*
* // Initialize the Datadog SDK.
* await DdSdkReactNative.initialize(...);
* A map of client names to their corresponding {@link FlagsClient} instances.
*
* // Optinal flags configuration object.
* const flagsConfig = {
* customFlagsEndpoint: 'https://flags.example.com'
* };
*
* // Enable the feature.
* await DdFlags.enable(flagsConfig);
*
* // Retrieve the client and access feature flags.
* const flagsClient = DdFlags.getClient();
* const flagValue = await flagsClient.getBooleanValue('new-feature', false);
* ```
*
* @param configuration Configuration options for the Datadog Flags feature.
* Each of these clients hold their own context and flags state.
*/
enable = async (configuration?: DdFlagsConfiguration): Promise<void> => {
if (configuration?.enabled === false) {
return;
}

if (this.isFeatureEnabled) {
InternalLog.log(
'Datadog Flags feature has already been enabled. Skipping this `DdFlags.enable()` call.',
SdkVerbosity.WARN
);
}
private clients: Record<string, FlagsClient> = {};

// Default `enabled` to `true`.
enable = async (configuration: FlagsConfiguration = {}): Promise<void> => {
await this.nativeFlags.enable({ enabled: true, ...configuration });

this.isFeatureEnabled = true;
};

/**
* Returns a `FlagsClient` instance for further feature flag evaluation.
*
* For most applications, you would need only one client. If you need multiple clients,
* you can retrieve a couple of clients with different names.
*
* @param clientName An optional name of the client to retrieve. Defaults to `'default'`.
*
* @example
* ```ts
* // Reminder: you need to initialize the SDK and enable the Flags feature before retrieving the client.
* const flagsClient = DdFlags.getClient();
*
* // Set the evaluation context.
* await flagsClient.setEvaluationContext({
* targetingKey: 'user-123',
* attributes: {
* favoriteFruit: 'apple'
* }
* });
*
* const flagValue = flagsClient.getBooleanValue('new-feature', false);
* ```
*/
getClient = (clientName: string = 'default'): FlagsClient => {
if (!this.isFeatureEnabled) {
InternalLog.log(
Expand Down
Loading