Integrating React Native 0.72.10 into an Existing Mobile App: Challenges, Project Structure, and Platform-specific Solutions
This article details the process of introducing React Native 0.72.10 into an existing mobile application, covering the rationale for selection, project structure adjustments, iOS and Android integration challenges, script modifications, dependency management, and solutions to common issues such as path configuration, Gradle compatibility, and library conflicts.
1 Background
Soul App uses a hybrid technology stack that includes native, web containers, and React Native. During development the team faced many challenges and accumulated practical experience. Introducing React Native is a key step, and adjusting the project structure determines the subsequent development model and engineering experience.
This article, based on React Native version 0.72.10, shares how to introduce React Native into an existing project.
2 Technical Selection
We chose React Native for three main reasons:
Dynamic updates: Frequent feature verification requires frequent client releases. React Native enables over‑the‑air updates without republishing the client.
Performance: Compared with a pure Hybrid H5 approach, React Native offers near‑native performance and experience.
Community maturity: Companies such as Tencent and Ctrip have mature RN applications, providing abundant reference material.
Considering these factors, we decided to use React Native 0.72.10 because:
The version supports the Hermes engine and the new architecture (Fabric, TurboModules).
iOS minimum version is 12.x; using RN 0.73.x would raise the minimum to 13.4, affecting a large user base.
3 Project Integration
Official recommended project structure:
project/
├── node_modules/ # all project dependencies
├── android/ # Android‑specific files
│ ├── app/ # main Android code
│ ├── build.gradle # build configuration
│ └── ...
├── ios/ # iOS‑specific files
│ ├── App/ # main iOS code
│ ├── Podfile # CocoaPods config
│ └── ...
├── src/ # React Native source code
│ ├── components/ # reusable React components
│ ├── screens/ # screen components
│ ├── navigation/ # navigation config
│ ├── assets/ # static assets
│ ├── utils/ # utility functions
│ ├── App.js # entry file
│ └── ...
├── index.js # entry point initializing RN app
├── package.json # metadata and dependencies
└── ...Because this structure tightly couples iOS and Android, we defined a more suitable iOS‑only layout:
project/
├── App/ # iOS main project code
├── Podfile/ # CocoaPods config
├── SoulRNSDK/ # native library for RN communication
└── ...We also kept a unified structure for shared RN source code:
project/
├── node_modules/ # dependencies
├── src/ # RN source
│ ├── components/
│ ├── screens/
│ ├── navigation/
│ ├── assets/
│ ├── utils/
│ ├── App.js
│ └── ...
├── index.js
├── package.json
└── ...We created a private library SoulRNSDK to handle native‑RN communication. Developers who do not work on RN can simply depend on the pre‑built library, while RN developers can switch the library to source mode for debugging.
iOS Integration
Problem 1: Missing Documentation
The official integration guide is outdated. For example, the pod specification for FBReactNativeSpec changed from:
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"to the newer:
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/React/FBReactNativeSpec"Community docs still reference the old path, requiring manual adjustments.
Problem 2: Project Scripts
RN uses react_native_pods.rb to install native dependencies. The script calls use_react_native , which reads package.json , finds podspec files, and configures pods accordingly. Autolinking works, but path changes broke the script lookup, so we modified the Podfile to invoke the script before dependency installation.
Problem 3: Path Configuration
The use_react_native! method is defined as:
def use_react_native!(
path: "../node_modules/react-native",
fabric_enabled: false,
new_arch_enabled: ENV['RCT_NEW_ARCH_ENABLED'] == '1',
production: ENV['PRODUCTION'] == '1',
hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
flipper_configuration: FlipperConfiguration.disabled,
app_path: '..',
config_file_dir: '',
ios_folder: 'ios'
)Key parameters:
path : location of the React Native library.
app_path : path to the main native project.
ios_folder : name of the iOS project folder.
Adjusting these alone was insufficient; the script also relies on require to locate Node modules, which failed after restructuring. By adding a custom script step in the Podfile , we dynamically patched the required files.
Problem 4: Dependency Overhead
Every run required a fresh node_modules installation, slowing builds for team members not working on RN. We therefore packaged RN‑related libraries into a private base library, separating engine libraries (stable) from third‑party libraries (frequently updated). An automation script now pulls the RN project, installs dependencies, patches scripts, merges libraries, and publishes the private package.
Automation script excerpt (path update):
npm install || exit 1
cd '..'
cp 'ios/soul_rn_script/native_modules.rb' 'source/node_modules/@react-native-community/cli-platform-ios/native_modules.rb'
cp 'ios/soul_rn_script/reanimated_utils.rb' 'source/node_modules/react-native-reanimated/scripts/reanimated_utils.rb'Copying libraries to the base directory:
find "$CompilePath" -type d -name "*.bundle" > bundles.txt
declare -a bundles=()
while IFS= read -r line; do
bundles+=("$line")
done < bundles.txt
rm bundles.txt
for bundle in "${bundles[@]}"; do
cp -r "$bundle" "$MergesPath"
doneAndroid Integration
Problem 1: Gradle Compatibility
Android Studio now defaults to Kotlin DSL, while most tutorials use Groovy DSL. Additionally, the project uses Gradle plugin 7.3.0 with Gradle 7.4.2, causing compatibility issues. We migrated the native‑module linking configuration to Kotlin DSL, e.g.:
val applyNativeModules: Closure
= extra.get("applyNativeModulesSettingsGradle") as Closure
applyNativeModules(settings)Problem 2: RN SDK Splitting
The desired structure is "Main Project" + "RN SDK" + "RN Project". We packaged RN dependencies (engine and third‑party) into AARs and published them to our Maven repository. Example dependencies:
dependencies {
// Other dependencies here
implementation "com.facebook.react:react-android"
implementation "com.facebook.react:hermes-android"
}Our internal Maven coordinates:
dependencies {
api 'cn.soul.android.lib:react-android:0.72.10-release'
api 'cn.soul.android.lib:hermes-android:0.72.10-release'
api 'com.facebook.fbjni:fbjni-java-only:0.2.2'
// ... other Facebook libraries
}Third‑party AARs (e.g., SVG, SafeAreaContext) are also published and referenced:
dependencies {
api 'cn.soul.android.lib:androidrnsvg-release:0.0.1'
api 'cn.soul.android.lib:safeareacontext-release:0.0.1'
}RN instance manager configuration:
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(application)
.setCurrentActivity(this)
.setJSMainModulePath("index")
.setJSBundleFile(bundlePath)
.addPackages(
SoulReactPackageList(application).getPackages().apply {
add(SoulReactPackage())
add(SvgPackage())
add(SafeAreaContextPackage())
}
)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build()Problem 3: RN Debugging Issues
Debug mode sometimes fails to load the bundle, producing:
com.facebook.react.common.DebugServerException:
facebook::react::Recoverable: Could not open file http://xxx.xxx:8081/index.bundle?platform=android: No such file or directoryThe cause was OkHttp intercepting the WebSocket connection. We duplicated the source and patched the interceptor to resolve the issue.
Debug steps:
Connect a device.
Open two terminals: run yarn start to launch the Metro server, and run adb reverse tcp:8081 tcp:8081 to forward the port.
Problem 4: Image Resource Paths
RN require resolves paths relative to the bundle, not the source file. Correct usage examples:
<Image style={{width:100,height:100}} source={require('./icon.png')} />For project‑wide assets:
<Image source={require('Project/images/icon.png')} />Both require the image to reside in the same directory as the bundle or be placed under the bundle root.
Problem 5: Crash on Re‑entering RN Page
After the first RN page load, returning to it caused a NoClassDefFoundError: com.facebook.react.jsexecutor.JSCExecutor . The fix was to configure the JavaScript executor correctly.
Problem 6: libc++_shared.so Conflict
Release builds crashed with UnsatisfiedLinkError due to multiple libraries loading different versions of libc++_shared.so . Using pickFirst 'lib/arm64-v8a/libc++_shared.so' was insufficient. The adopted compromise was to replace dynamic dependencies with static ones for the conflicting libraries, keeping the RN‑provided libc++_shared.so unchanged.
4 Future Plans
The current RN integration is a small step on the RN side of the container. Future work will focus on RN stability, performance, and dynamic code‑splitting based on the actual needs of Soul App.
Thank you for reading. If you enjoyed the article, feel free to follow, comment, or like.
Soul Technical Team
Technical practice sharing from Soul
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.