Skip to content
Merged
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
32 changes: 29 additions & 3 deletions implementations/react-native-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ for the entry contract.

## Prerequisites

- Node.js >= 20.19.0 (24.15.0 recommended to match `.nvmrc`)
- Node.js 24.15.0, matching the repository's `.nvmrc`
- pnpm
- Android SDK, `adb`, and at least one configured Android emulator for Android Detox flows. The
- JDK 17, Android SDK platform/build tools 37, `adb`, and at least one configured Android emulator for Android Detox flows. The
one-shot runner and Detox can launch a configured emulator; it does not need to be running before
you start the flow.
- Xcode with an iOS Simulator available for iOS Detox flows
- Xcode 26 or later, CocoaPods, and an iOS Simulator available for iOS Detox flows
Comment thread
phobetron marked this conversation as resolved.

## Setup

Expand Down Expand Up @@ -197,6 +197,32 @@ Local Android E2E logging is intentionally minimal by default.
- `implementations/react-native-sdk/logs/device.log`
- `implementations/react-native-sdk/logs/test-results.log`

## Upgrading React Native

Treat React Native and its associated tooling as one upgrade. Renovate groups the implementation's
React Native dependencies and native build tools into a reviewed PR with automerge disabled. The
group collects available updates; it does not select compatible versions or migrate native project
files.

1. Compare the installed and target React Native releases with the
[Upgrade Helper](https://react-native-community.github.io/upgrade-helper/) and the target release's
[community template](https://github.com/react-native-community/template). Update the runtime,
Babel preset, Codegen, Gradle plugin, Metro config, and Jest preset together. Select compatible
React, test renderer, CLI, and native library versions from the target release's requirements.
2. Apply the required Android and iOS project changes, including Gradle, Kotlin, Android SDK, and
Xcode requirements. Preserve the implementation's SDK integration and Detox configuration.
3. Install through `pnpm implementation:run -- react-native-sdk implementation:install`. The
implementation uses the repository's generated SDK tarballs and does not track a pnpm lockfile.
Hermes compiler selection belongs to React Native; the implementation hoists that dependency for
the Gradle plugin instead of pinning it independently.
4. Refresh iOS pods from `implementations/react-native-sdk` with `bundle install` and
`bundle exec pod update --no-repo-update --project-directory=ios`. Review the generated
`ios/Podfile.lock` for changes outside the upgrade and include it with the native project changes.
5. Run the implementation's lint and typecheck commands, Android and iOS release builds, and the
maintained Detox flows before merging. Use `test:e2e:android:build:release` for Android and the
`ios.sim.release` Detox configuration for iOS. Verify navigation, tracking, offline behavior, and
the preview panel; a successful dependency install alone does not establish compatibility.

## Related

- [@contentful/optimization-react-native](../../packages/react-native-sdk/README.md) - React Native
Expand Down
5 changes: 3 additions & 2 deletions implementations/react-native-sdk/__tests__/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import App from '../App'
// Note: import explicitly to use the types shipped with jest.
import { it } from '@jest/globals'

import { render } from '@testing-library/react-native'
import { render, screen } from '@testing-library/react-native'

it('renders correctly', () => {
it('renders the loading state after SDK initialization', async () => {
render(<App />)
await screen.findByText('Loading...')
})
6 changes: 3 additions & 3 deletions implementations/react-native-sdk/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ def enableProguardInReleaseBuilds = false
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
* `def jscFlavor = 'io.github.react-native-community:jsc-android-intl:2026004.+'`
*
* 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:+'
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'

android {
ndkVersion rootProject.ext.ndkVersion
Expand Down Expand Up @@ -104,7 +104,7 @@ android {
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
}
}
packagingOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,24 @@ import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader

class MainApplication : Application(), ReactApplication {

override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
}

override fun getJSMainModuleName(): String = "index"

override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG

override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}

override val reactHost: ReactHost
get() = getDefaultReactHost(applicationContext, reactNativeHost)
override val reactHost: ReactHost by lazy {
getDefaultReactHost(
context = applicationContext,
packageList =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
},
)
}

override fun onCreate() {
super.onCreate()
SoLoader.init(this, OpenSourceMergedSoMapping)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
loadReactNative(this)
}
}
6 changes: 3 additions & 3 deletions implementations/react-native-sdk/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
buildscript {
ext {
buildToolsVersion = "36.0.0"
buildToolsVersion = "37.0.0"
minSdkVersion = 24
compileSdkVersion = 36
compileSdkVersion = 37
targetSdkVersion = 36
ndkVersion = "27.1.12297006"
kotlinVersion = "2.1.20"
kotlinVersion = "2.2.0"
}
repositories {
google()
Expand Down
4 changes: 4 additions & 0 deletions implementations/react-native-sdk/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ newArchEnabled=true
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true

# React Native 0.87 uses the Kotlin plugin and legacy DSL with AGP 9.
android.builtInKotlin=false
android.newDsl=false
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,18 +578,24 @@
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = (
"$(inherited)",
"-DRCT_REMOVE_LEGACY_ARCH=1",
);
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
"-DRCT_REMOVE_LEGACY_ARCH=1",
);
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
PODFILE_DIR = "$(SRCROOT)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
Expand Down Expand Up @@ -650,18 +656,24 @@
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = (
"$(inherited)",
"-DRCT_REMOVE_LEGACY_ARCH=1",
);
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
"-DRCT_REMOVE_LEGACY_ARCH=1",
);
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
PODFILE_DIR = "$(SRCROOT)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = @"OptimizationImplementationApp";
self.dependencyProvider = [RCTAppDependencyProvider new];
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>RCTNewArchEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
Expand Down
9 changes: 9 additions & 0 deletions implementations/react-native-sdk/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,14 @@ target 'OptimizationImplementationApp' do
:mac_catalyst_enabled => false,
# :ccache_enabled => true
)

# Align AsyncStorage's iOS 13 resource bundle with the app minimum for Xcode 27.
installer.pods_project.targets.each do |target|
next unless target.name == 'AsyncStorage-AsyncStorage_resources'

target.build_configurations.each do |configuration|
configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = min_ios_version_supported
end
end
end
end
Loading
Loading