-
Notifications
You must be signed in to change notification settings - Fork 37
chore: Add FDv2 example project for react-native. #1277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f44eae2
6f8f006
ca00a4f
21b3f73
cf1c166
fce5b09
7e00858
3f9c780
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| MOBILE_KEY= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| > Why do I have a folder named ".expo" in my project? | ||
| The ".expo" folder is created when an Expo project is started using "expo start" command. | ||
| > What do the files contain? | ||
| - "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds. | ||
| - "settings.json": contains the server configuration that is used to serve the application manifest. | ||
| > Should I commit the ".expo" folder? | ||
| No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine. | ||
| Upon project creation, the ".expo" folder is already added to your ".gitignore" file. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "devices": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { MOBILE_KEY } from '@env'; | ||
|
|
||
| import { | ||
| AutoEnvAttributes, | ||
| LDProvider, | ||
| ReactNativeLDClient, | ||
| } from '@launchdarkly/react-native-client-sdk'; | ||
|
|
||
| import Welcome from './src/welcome'; | ||
|
|
||
| const featureClient = new ReactNativeLDClient(MOBILE_KEY, AutoEnvAttributes.Enabled, { | ||
| debug: true, | ||
| applicationInfo: { | ||
| id: 'ld-rn-fdv2-test-app', | ||
| version: '0.0.1', | ||
| }, | ||
| baseUri: 'http://192.168.7.152:3002/proxy-poll', | ||
| streamUri: 'http://192.168.7.152:3001/proxy', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded local IP addresses in example app configMedium Severity The Reviewed by Cursor Bugbot for commit 3f9c780. Configure here. |
||
| // @ts-ignore dataSystem is @internal | ||
| dataSystem: {}, | ||
| }); | ||
|
|
||
| const App = () => ( | ||
| <LDProvider client={featureClient}> | ||
| <Welcome /> | ||
| </LDProvider> | ||
| ); | ||
|
|
||
| export default App; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # LaunchDarkly React Native SDK - FDv2 Example App | ||
|
|
||
| This is a minimal example app that demonstrates the experimental FDv2 data system | ||
| for the React Native SDK. | ||
|
|
||
| > **Note:** FDv2 support is `@internal` and experimental. It is not ready for | ||
| > production use and may change or be removed without notice. | ||
|
|
||
| ## Features Demonstrated | ||
|
|
||
| - SDK initialization with the `dataSystem` option (FDv2 protocol) | ||
| - Connection mode switching for all FDv2 modes: | ||
| - **Streaming** - real-time flag updates with polling fallback | ||
| - **Polling** - periodic polling only | ||
| - **Offline** - cached flags only, no network | ||
| - **One-Shot** - initialize then stop (no persistent synchronizer) | ||
| - **Background** - low-frequency polling for background state | ||
| - **Automatic** - clear the override and use automatic mode selection | ||
| - Context identification | ||
| - Boolean flag evaluation | ||
|
|
||
| ## Quickstart | ||
|
|
||
| 1. At the js-core repo root, install dependencies and build: | ||
|
|
||
| ```shell | ||
| yarn && yarn build | ||
| ``` | ||
|
|
||
| 2. Create an `.env` file in this directory (`example-fdv2/`) with your mobile key: | ||
|
|
||
| ```shell | ||
| MOBILE_KEY=mob-your-mobile-key-here | ||
| ``` | ||
|
|
||
| 3. Update the flag key in `src/welcome.tsx` if needed (defaults to `sample-feature`). | ||
|
|
||
| 4. Run the app: | ||
|
|
||
| ```shell | ||
| # iOS | ||
| yarn ios | ||
|
|
||
| # Android | ||
| yarn android | ||
| ``` | ||
|
|
||
| > **Note:** You may need to run `npx expo prebuild` before the first iOS or | ||
| > Android build. | ||
|
|
||
| ## Caveats | ||
|
|
||
| - **Network-based automatic mode switching** is not yet implemented. The wiring | ||
| is in place, but `RNStateDetector` does not yet emit network state changes. | ||
| Lifecycle-based switching (foreground/background) works. | ||
| - The `dataSystem` option and `setConnectionMode()` are marked `@internal` and | ||
| require `@ts-ignore` to use from TypeScript. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # OSX | ||
| # | ||
| .DS_Store | ||
|
|
||
| # Android/IntelliJ | ||
| # | ||
| build/ | ||
| .idea | ||
| .gradle | ||
| local.properties | ||
| *.iml | ||
| *.hprof | ||
| .cxx/ | ||
|
|
||
| # Bundle artifacts | ||
| *.jsbundle |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| apply plugin: "com.android.application" | ||
| apply plugin: "org.jetbrains.kotlin.android" | ||
| apply plugin: "com.facebook.react" | ||
|
|
||
| def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() | ||
|
|
||
| /** | ||
| * This is the configuration block to customize your React Native Android app. | ||
| * By default you don't need to apply any configuration, just uncomment the lines you need. | ||
| */ | ||
| react { | ||
| entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) | ||
| reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() | ||
| hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" | ||
| codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() | ||
|
|
||
| // Use Expo CLI to bundle the app, this ensures the Metro config | ||
| // works correctly with Expo projects. | ||
| cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) | ||
| bundleCommand = "export:embed" | ||
|
|
||
| /* Folders */ | ||
| // The root of your project, i.e. where "package.json" lives. Default is '../..' | ||
| // root = file("../../") | ||
| // The folder where the react-native NPM package is. Default is ../../node_modules/react-native | ||
| // reactNativeDir = file("../../node_modules/react-native") | ||
| // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen | ||
| // codegenDir = file("../../node_modules/@react-native/codegen") | ||
|
|
||
| /* Variants */ | ||
| // The list of variants to that are debuggable. For those we're going to | ||
| // skip the bundling of the JS bundle and the assets. By default is just 'debug'. | ||
| // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. | ||
| // debuggableVariants = ["liteDebug", "prodDebug"] | ||
|
|
||
| /* Bundling */ | ||
| // A list containing the node command and its flags. Default is just 'node'. | ||
| // nodeExecutableAndArgs = ["node"] | ||
|
|
||
| // | ||
| // The path to the CLI configuration file. Default is empty. | ||
| // bundleConfig = file(../rn-cli.config.js) | ||
| // | ||
| // The name of the generated asset file containing your JS bundle | ||
| // bundleAssetName = "MyApplication.android.bundle" | ||
| // | ||
| // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' | ||
| // entryFile = file("../js/MyApplication.android.js") | ||
| // | ||
| // A list of extra flags to pass to the 'bundle' commands. | ||
| // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle | ||
| // extraPackagerArgs = [] | ||
|
|
||
| /* Hermes Commands */ | ||
| // The hermes compiler command to run. By default it is 'hermesc' | ||
| // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" | ||
| // | ||
| // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" | ||
| // hermesFlags = ["-O", "-output-source-map"] | ||
|
|
||
| /* Autolinking */ | ||
| autolinkLibrariesWithApp() | ||
| } | ||
|
|
||
| /** | ||
| * Set this to true to Run Proguard on Release builds to minify the Java bytecode. | ||
| */ | ||
| def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() | ||
|
|
||
| /** | ||
| * The preferred build flavor of JavaScriptCore (JSC) | ||
| * | ||
| * For example, to use the international variant, you can use: | ||
| * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` | ||
| * | ||
| * The international variant includes ICU i18n library and necessary data | ||
| * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that | ||
| * give correct results when using with locales other than en-US. Note that | ||
| * this variant is about 6MiB larger per architecture than default. | ||
| */ | ||
| def jscFlavor = 'org.webkit:android-jsc:+' | ||
|
|
||
| android { | ||
| ndkVersion rootProject.ext.ndkVersion | ||
|
|
||
| buildToolsVersion rootProject.ext.buildToolsVersion | ||
| compileSdk rootProject.ext.compileSdkVersion | ||
|
|
||
| namespace 'com.anonymous.reactnativeexamplefdv2' | ||
| defaultConfig { | ||
| applicationId 'com.anonymous.reactnativeexamplefdv2' | ||
| minSdkVersion rootProject.ext.minSdkVersion | ||
| targetSdkVersion rootProject.ext.targetSdkVersion | ||
| versionCode 1 | ||
| versionName "1.0.0" | ||
| } | ||
| signingConfigs { | ||
| debug { | ||
| storeFile file('debug.keystore') | ||
| storePassword 'android' | ||
| keyAlias 'androiddebugkey' | ||
| keyPassword 'android' | ||
| } | ||
| } | ||
| buildTypes { | ||
| debug { | ||
| signingConfig signingConfigs.debug | ||
| } | ||
| release { | ||
| // Caution! In production, you need to generate your own keystore file. | ||
| // see https://reactnative.dev/docs/signed-apk-android. | ||
| signingConfig signingConfigs.debug | ||
| shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) | ||
| minifyEnabled enableProguardInReleaseBuilds | ||
| proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" | ||
| crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true) | ||
| } | ||
| } | ||
| packagingOptions { | ||
| jniLibs { | ||
| useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false) | ||
| } | ||
| } | ||
| androidResources { | ||
| ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~' | ||
| } | ||
| } | ||
|
|
||
| // Apply static values from `gradle.properties` to the `android.packagingOptions` | ||
| // Accepts values in comma delimited lists, example: | ||
| // android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini | ||
| ["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> | ||
| // Split option: 'foo,bar' -> ['foo', 'bar'] | ||
| def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); | ||
| // Trim all elements in place. | ||
| for (i in 0..<options.size()) options[i] = options[i].trim(); | ||
| // `[] - ""` is essentially `[""].filter(Boolean)` removing all empty strings. | ||
| options -= "" | ||
|
|
||
| if (options.length > 0) { | ||
| println "android.packagingOptions.$prop += $options ($options.length)" | ||
| // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' | ||
| options.each { | ||
| android.packagingOptions[prop] += it | ||
| } | ||
| } | ||
| } | ||
|
|
||
| dependencies { | ||
| // The version of react-native is set by the React Native Gradle Plugin | ||
| implementation("com.facebook.react:react-android") | ||
|
|
||
| def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; | ||
| def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; | ||
| def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; | ||
|
|
||
| if (isGifEnabled) { | ||
| // For animated gif support | ||
| implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}") | ||
| } | ||
|
|
||
| if (isWebpEnabled) { | ||
| // For webp support | ||
| implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}") | ||
| if (isWebpAnimatedEnabled) { | ||
| // Animated webp support | ||
| implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}") | ||
| } | ||
| } | ||
|
|
||
| if (hermesEnabled.toBoolean()) { | ||
| implementation("com.facebook.react:hermes-android") | ||
| } else { | ||
| implementation jscFlavor | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Add project specific ProGuard rules here. | ||
| # By default, the flags in this file are appended to flags specified | ||
| # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt | ||
| # You can edit the include path and order by changing the proguardFiles | ||
| # directive in build.gradle. | ||
| # | ||
| # For more details, see | ||
| # http://developer.android.com/guide/developing/tools/proguard.html | ||
|
|
||
| # react-native-reanimated | ||
| -keep class com.swmansion.reanimated.** { *; } | ||
| -keep class com.facebook.react.turbomodule.** { *; } | ||
|
|
||
| # Add any project specific keep options here: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| xmlns:tools="http://schemas.android.com/tools"> | ||
|
|
||
| <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> | ||
|
|
||
| <application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" /> | ||
| </manifest> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| <uses-permission android:name="android.permission.INTERNET"/> | ||
| <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | ||
| <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> | ||
| <uses-permission android:name="android.permission.VIBRATE"/> | ||
| <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | ||
| <queries> | ||
| <intent> | ||
| <action android:name="android.intent.action.VIEW"/> | ||
| <category android:name="android.intent.category.BROWSABLE"/> | ||
| <data android:scheme="https"/> | ||
| </intent> | ||
| </queries> | ||
| <application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true"> | ||
| <meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/> | ||
| <meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/> | ||
| <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/> | ||
| <activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="portrait"> | ||
| <intent-filter> | ||
| <action android:name="android.intent.action.MAIN"/> | ||
| <category android:name="android.intent.category.LAUNCHER"/> | ||
| </intent-filter> | ||
| <intent-filter> | ||
| <action android:name="android.intent.action.VIEW"/> | ||
| <category android:name="android.intent.category.DEFAULT"/> | ||
| <category android:name="android.intent.category.BROWSABLE"/> | ||
| <data android:scheme="com.anonymous.reactnativeexamplefdv2"/> | ||
| </intent-filter> | ||
| </activity> | ||
| </application> | ||
| </manifest> |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Committed
.expodirectory that should be gitignoredLow Severity
The
.expodirectory is committed, but its ownREADME.mdexplicitly states it should not be shared and is machine-specific. The existingexamplehas a.gitignorethat excludes.expo/, butexample-fdv2is missing a.gitignoreentirely, causing these files to be tracked.Additional Locations (1)
packages/sdk/react-native/example-fdv2/.expo/devices.json#L1-L3Reviewed by Cursor Bugbot for commit 3f9c780. Configure here.