Let’s start with answering the question: what is a brownfield application?
The term “brownfield” implies something built with already existing elements. Originally used in urban planning to describe areas that can be repurposed, it was later adopted in mobile app development. A brownfield app is basically an app which is not built from scratch.
What does it mean in practice? It means that we want to run a React Native application as a part of a native app that is already developed with technologies like Java, Kotlin, Objective-C or Swift. In other words, we want to integrate React Native in an existing app.
How does it work?
Let’s assume that we have a native iOS application where we want to create a new screen using react-native app that is already implemented. That screen will navigate to another screen inside the react-native application.

In order to make it works, we need to do a few things.
Add React Native to the project:
- First of all, we need to add our React Native app to an existing native project. We will want to decide what parts of the React Native framework you would like to integrate. We will use CocoaPods for it. Let’s edit or create Podfile in the native project and add React and all needed subspecs with path pointed to
node_modules
and runpod install
command
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
platform :ios, '9.0' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' target 'NativeExample' do pod 'React', :path => '../node_modules/react-native/' pod 'React-Core', :path => '../node_modules/react-native/React' pod 'React-DevSupport', :path => '../node_modules/react-native/React' pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS' pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings' pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text' pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration' pod 'React-RCTWebSocket', :path => '../node_modules/react-native/Libraries/WebSocket' pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi' pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor' pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector' pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga' use_native_modules!('../..') end |
Run a React Native app:
- When the React Native app is properly linked to the native one, we need to create a class that implements
RCTBridgeDelegate
– at least thesourceURLForBridge
method that is needed to specify which JS bundle will be used.
1 2 3 |
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; } |
- Then create a React Native bridge instance (
RCTBridge
) with the previously implemented class as the delegate andlaunchingOptions
dictionary if needed.
1 |
bridge = [[RCTBridge alloc] initWithDelegate:delegate launchOptions:launchOptions]; |
- Create
ViewController
where we will set therootView
of the react-native app as a view property.
1 2 3 |
RCTRootView *reactView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"MainComponent" initialProperties:nil] UIViewController *vc = [[UIViewController alloc] init]; vc.view = rootView; |
- Finally, we can push our react-native view controller to the stack
1 |
[self presentViewController:vc animated:YES completion:nil]; |
Additional steps:
Note: In debug mode, we need packager to serve JS bundle. However, Apple has blocked implicit cleartext HTTP resource loading. So we need to add the following our project’s Info.plist
(or equivalent) file.
1 2 3 4 5 6 7 8 9 10 11 |
<key>NSAppTransportSecurity</key> <dict> <key>NSExceptionDomains</key> <dict> <key>localhost</key> <dict> <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key> <true/> </dict> </dict> </dict> |
In addition, we can add scripts to Build Phases
which will build react-native bundle and images for us

export NODE_BINARY=node ../node_modules/react-native/scripts/react-native-xcode.sh
And the script that will run the packager

1 2 3 4 5 6 7 8 9 10 11 12 13 |
export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}" echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${SRCROOT}/../node_modules/react-native/scripts/.packager.env" if [ -z "${RCT_NO_LAUNCH_PACKAGER+xxx}" ] ; then if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly" exit 2 fi else open "$SRCROOT/../node_modules/react-native/scripts/launchPackager.command" || echo "Can't start packager automatically" fi fi |
Navigation issues:
We have all set, however, there is an issue with swipe to back
functionality. We are not able to get back to our native screen via a swipe gesture or by means of Go Back
button. We need some mechanism that would make possible to say from react-native
to the native part that we want back to our native screen or that we want to enable/disable native PopGestureRecognizer
. It could be solved by a react-native native module which will send notification by NSNotificationCenter
.
BUT wait… Can’t it be done in simpler way? Do we really need to take care of RCTBridgeDelegate
, RCTBridge
, RCTRootView
?
Of course, it might be easier 🙂 We can use react-native-brownfield
package which will help us with navigation’s issues and provide a nice API to run a react-native application.
How does it work with React Native brownfield?
We want to achieve exactly the same effect as before but we will use react-native-brownfield
library

Add React Native to the project:
The theory is the same as I described in the previous part of this article but with some facilities. In order to run our React Native app in a native one using react-native-brownfield we need to add that package as a dependency to package.json
using:
1 |
npm install @callstack/react-native-brownfield |
or
1 |
yarn add @callstack/react-native-brownfield |
Next, we need to add the React Native app as we did in Add react-native to project
point using CocoaPods.
Run a React Native app:
And now the fun begins 🙂 Instead of creating a bridge by ourselves we can simply use ReactNativeBrownfield
package.
Import ReactNativeBrownfield
package and create the instance of it with the entry file name.
1 2 |
ReactNativeBrownfield *reactNativeBrownfieldManager = [ReactNativeBrownfield shared]; reactNativeBrownfieldManager.entryFile = @"index" |
We are ready to run react-native app using startReactNative
method of our reactNativeBrownfieldManager
1 2 3 |
[reactNativeBrownfieldManager startReactNative:^(void){ NSLog(@"React Native started"); }]; |
And that’s it. We can push react-native app view controller to the navigation stack with user-friendly API.
1 |
[[self navigationController] pushViewController:[[ReactNativeViewController alloc] initWithModuleName:@"MainComponent"] animated:YES]; |
Additional steps:
All additional steps are needed to be added here as well
- Edit
Info.plist
file to allow connection to the packager - Add script to
Build Phases
to run packager - Add script to
BuildPhases
to bundle JS code and assets
Navigation issues:
react-native-brownfield
comes with a solution for the issues mentioned above. It exposes two methods that can be used in JS code to toggle back gesture and pop to a native screen.
ReactNativeBrownfield.setNativeBackGestureAndButtonEnabled
– to toggle iOS native back gestureReactNativeBrownfield.popToNative
– to pop to the native screen
And voilà! We are ready to go 🙂
Conclusion:
It is a popular thing to use React Native within existing native apps especially by larger organizations that have multiple teams working on the same app for multiple platforms. Thanks to React Native brownfield the integration is easier, enjoyable and some issues that exist in every React Native brownfield app are solved 🙂
Working examples of using react-native-brownfield are inside example
directory.