iOS Brownfield App With React Native in an Easy Way

Authors
Piotr Drapich
Software Engineer
@
Callstack

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.

video showing example app without react-native-brownfield

In order to make it works, we need to do a few things.

Adding 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 <rte-code>node_modules<rte-code> and run <rte-code>pod install<rte-code> command.
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
view raw gistfile1.txt hosted with ❤ by GitHub

Running a React Native app

  • When the React Native app is properly linked to the native one, we need to create a class that implements <rte-code>RCTBridgeDelegate<rte-code> - at least the <rte-code>sourceURLForBridge<rte-code> method that is needed to specify which JS bundle will be used.
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
}
view raw gistfile1.txt hosted with ❤ by GitHub
  • Then create a React Native bridge instance ( <rte-code>RCTBridge<rte-code> ) with the previously implemented class as the delegate and <rte-code>launchingOptions<rte-code> dictionary if needed.
bridge = [[RCTBridge alloc] initWithDelegate:delegate launchOptions:launchOptions];
view raw gistfile1.txt hosted with ❤ by GitHub
  • Create <rte-code>ViewController<rte-code> where we will set the <rte-code>rootView<rte-code> of the react-native app as a view property.
RCTRootView *reactView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"MainComponent" initialProperties:nil]
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
view raw gistfile1.txt hosted with ❤ by GitHub
  • Finally, we can push our react-native view controller to the stack
[self presentViewController:vc animated:YES completion:nil];
view raw gistfile1.txt hosted with ❤ by GitHub

Additional steps

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 <rte-code>Info.plist<rte-code> (or equivalent) file.

<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
view raw gistfile1.txt hosted with ❤ by GitHub

In addition, we can add scripts to <rte-code>Build Phases<rte-code> which will build react-native bundle and images for us:

screenshot with adding scripts to build phases

And the script that will run the packager:

screenshot with script that will run the packager
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
view raw gistfile1.txt hosted with ❤ by GitHub

Navigation issues

We have set it all right now, however, there is an issue with <rte-code>swipe to back<rte-code> functionality. We are not able to get back to our native screen via a swipe gesture or by means of <rte-code>Go Back<rte-code> button. We need some mechanism that would make possible to say from <rte-code>react-native<rte-code> to the native part that we want back to our native screen or that we want to enable/disable native <rte-code>PopGestureRecognizer<rte-code>. It could be solved by a react-native native module which will send notification by <rte-code>NSNotificationCenter<rte-code>.

BUT wait... Can't it be done in simpler way? Do we really need to take care of <rte-code>RCTBridgeDelegate<rte-code>, <rte-code>RCTBridge<rte-code>, <rte-code>RCTRootView<rte-code>?

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 <rte-code>react-native-brownfield<rte-code> library.

video showing example app with react-native-brownfield

Adding 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 <rte-code>package.json<rte-code> using:

npm install @callstack/react-native-brownfield
view raw gistfile1.txt hosted with ❤ by GitHub

or

yarn add @callstack/react-native-brownfield
view raw gistfile1.txt hosted with ❤ by GitHub

Next, we need to add the React Native app as we did in <rte-code>Add react-native to project<rte-code> point using CocoaPods.

Running a React Native app

And now the fun begins :) Instead of creating a bridge by ourselves we can simply use <rte-code>ReactNativeBrownfield<rte-code> package.

Import <rte-code>ReactNativeBrownfield<rte-code> package and create the instance of it with the entry file name.

ReactNativeBrownfield *reactNativeBrownfieldManager = [ReactNativeBrownfield shared];
reactNativeBrownfieldManager.entryFile = @"index"
view raw gistfile1.txt hosted with ❤ by GitHub

We are ready to run react-native app using <rte-code>startReactNative<rte-code> method of our <rte-code>reactNativeBrownfieldManager<rte-code>

[reactNativeBrownfieldManager startReactNative:^(void){
NSLog(@"React Native started");
}];
view raw gistfile1.txt hosted with ❤ by GitHub

And that's it. We can push react-native app view controller to the navigation stack with user-friendly API.

[[self navigationController] pushViewController:[[ReactNativeViewController alloc] initWithModuleName:@"MainComponent"] animated:YES];
view raw gistfile1.txt hosted with ❤ by GitHub

Additional steps

All additional steps are needed to be added here as well

  • Edit <rte-code>Info.plist<rte-code> file to allow connection to the packager
  • Add script to <rte-code>Build Phases<rte-code> to run packager
  • Add script to <rte-code>BuildPhases<rte-code> to bundle JS code and assets

Navigation issues

<rte-code>react-native-brownfield<rte-code> 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.

  • <rte-code>ReactNativeBrownfield.setNativeBackGestureAndButtonEnabled<rte-code> - to toggle iOS native back gesture
  • <rte-code>ReactNativeBrownfield.popToNative<rte-code> - 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.

As part of The React Native Show, we released a podcast fully dedicated to future of Brownfield Development with React Native.

Insights

Learn more about Brownfield

cover

Aug 27Article

React Native in .NET MAUI: A Practical Brownfield Integration Guide

Learn how to embed React Native inside a .NET MAUI app with a brownfield setup. This guide covers runtime packaging, lifecycle wiring, navigation, and shared state across both runtimes.

cover

Aug 3Article

Migration to React Native in 2026 Starts With a Delivery Question

Choosing between brownfield and greenfield React Native migration? Learn where risk sits, when to use a checkpoint, and what to prove before committing.

cover

Jul 8Article

How we made Hybrid iOS apps compile 39% faster with React Native Brownfield

See how React Native Brownfield uses iOS prebuilts to cut packaging times by up to 39% for hybrid apps, with details on setup, internals, and benchmark results.

cover

Jun 18Article

How We Made the Brownfield Gradle Plugin 22% Faster

See how task-driven Gradle optimization made the Brownfield Gradle Plugin 22% faster for clean Android release builds.

cover

Jun 3Article

AI-Supported Brownfield Migration to React Native

See how AI-supported workflows cut brownfield React Native evaluation from weeks to days by speeding up setup, planning, implementation, and QA.

cover

May 18Article

Unlocking Expo Updates in an Isolated Brownfield Architecture with SDK 55

Learn how to enable Expo Updates in an Isolated Brownfield setup with Expo SDK 55, including iOS packaging, Android patching, and OTA testing.

cover

Apr 23Talk

State of Brownfield in React Native

A practical look at Brownfield React Native adoption in mature native apps, architecture, tooling, and real-world tradeoffs.

cover

Apr 2Webinar

AI-Assisted Migrations to React Native: From Months to Days

Reduce migration time from months to days by using AI in real React Native workflows. Learn when to migrate incrementally and when starting fresh is the better option.

cover

Nov 17Talk

Vega Developer Meetup

Learn how to integrate React Native into Android TV apps at the Vega Developer Meetup. Workshop, roadmap, and community talks, live in Munich.

cover

May 16Podcast

You Don’t Need a Rewrite, You Need React Native Brownfield

Discover how modern React Native Brownfield integration enables cross-platform power in native apps, without rewriting from scratch.

cover

Jan 13Podcast

Lessons from Shopify's Migration to React Native with Mustafa Ali

Learn how Shopify's migration to React Native led to 86% code unification, reduced build times, and seamless feature parity across platforms.

cover

Jun 14Podcast

Migration to React Native

The 13th episode of React Universe On Air (formerly known as The React Native Show) is fully focused on migrating from different technologies to React Native, the two most popular approaches to this process (greenfield and brownfield software development), and our state-of-the-art brownfield approach.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.