In the beginning, we have to answer the question: What is a brownfield app?
The term brownfield was originally used in construction and development to reference land that at some point was occupied by a permanent structure. In a brownfield project, the structure would need to be demolished or renovated. Today, the term brownfield project is used in many industries, including software development, to mean to start a project based on prior work or to rebuild (engineer) a product from an existing one.
~Vangie Beal
So in our case, it means that we want to run a react-native application as a part of a native one that is already developed with technologies like Java or Kotlin.
How does it work?
Let’s assume that we have a native Android 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 an entry for the local React Native maven directory to the project’s
build.gradle
. Let’s add it to theallprojects
block, above other maven repositories
1 2 3 4 5 6 7 8 |
allprojects { repositories { maven { // All of React Native (JS, Android binaries) is installed from npm url "$rootDir/../node_modules/react-native/android" } } } |
- Add
react-native
fromnode_modules
(thanks to change from the previous point) as a dependency to app’sbuild.gradle
1 2 3 4 |
dependencies { implementation 'com.android.support:appcompat-v7:27.1.1' implementation "com.facebook.react:react-native:+" } |
- Add autolinking command to at the end of app’s
build.gradle
1 2 |
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project, "../") |
Run react-native app:
In order to run our react-native app we are going to:
- Create an
Activity
that implementsDefaultHardwareBackBtnHandler
interface and usesTheme.AppCompat.Light.NoActionBar
theme because some React Native UI components rely on it.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class ReactNativeActivity extends Activity implements DefaultHardwareBackBtnHandler { private ReactRootView mReactRootView; private ReactInstanceManager mReactInstanceManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void invokeDefaultOnBackPressed() { super.onBackPressed(); } } |
1 2 3 4 |
<activity android:name=".ReactNativeActivity" android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> |
- Create
ReactRootView
instance insideonCreate
method
1 |
mReactRootView = new ReactRootView(this); |
- Create
ReactInstanceManager
instance with needed parameters
1 2 3 4 5 6 |
mReactInstanceManager = ReactInstanceManager.builder() .setCurrentActivity(this) .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModulePath("index") .build(); |
- Start react-native application within created root view using
startReactApplication
method where the second parameter is the same string as inAppRegistry.registerComponent()
inindex.js
file.
1 2 |
mReactRootView.startReactApplication(mReactInstanceManager, "MainComponent", null); |
- Set react-native root view as activity content view
1 |
setContentView(mReactRootView); |
Now we are able to run our react-native app by starting ReactNativeActivity
but we need still add some code to get all things to work 🙂
- Pass activity lifecycle callbacks to the
ReactInstanceManager
andReactRootView
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 |
@Override protected void onPause() { super.onPause(); if (mReactInstanceManager != null) { mReactInstanceManager.onHostPause(this); } } @Override protected void onResume() { super.onResume(); if (mReactInstanceManager != null) { mReactInstanceManager.onHostResume(this, this); } } @Override protected void onDestroy() { super.onDestroy(); if (mReactInstanceManager != null) { mReactInstanceManager.onHostDestroy(this); } if (mReactRootView != null) { mReactRootView.unmountReactApplication(); } } |
- Pass the back button events to avoid closing ReactNativeActivity when hardware back button is pressed and support hardware back button on JS side
1 2 3 4 5 6 7 8 |
@Override public void onBackPressed() { if (mReactInstanceManager != null) { mReactInstanceManager.onBackPressed(); } else { super.onBackPressed(); } } |
Note: Even with this change we don’t have a mechanism to back to native screen w/o using hardware back button. This issue could be solved by react-native native module which will expose the method to JS that will finish ReactNativeActivity
.
Additional points:
- In debug mode, we need packager to serve JS bundle. To make it possible we need internet permissions. Add the following line to the
AndroidManifest.xml
file.
1 |
<uses-permission android:name="android.permission.INTERNET" /> |
Note: Starting with Android 9 (API level 28), cleartext traffic is disabled by default. This prevents your application from connecting to the React Native packager. To solve this we need to add usesCleartextTraffic
option to your Debug AndroidManifest.xml
.
1 2 3 4 |
<application android:usesCleartextTraffic="true" tools:targetApi="28" /> |
- To enable Dev Menu in react-native app add DevSettingsActivity to
AndroidManifest.xml
1 |
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> |
All of the additional points are required only for debug builds where we reload JS code from the development server, so you can strip this in release builds.
I think that all of those steps above are repeated in almost all react-native brownfield android app. But can’t it be done a bit simpler? Do we really need care about passing onPause
, onDestroy
etc?
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 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 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 react-native app as we did in Add react-native to project
point.
Additional steps:
All additional steps are needed to be added here as well and only for debug builds.
Run react-native app:
And now the fun begins 🙂 Instead of creating a root view and instance manager by ourselves we can simply use ReactNativeBrownfield
package. Since autolinking
script to build.gradle
and react-native-brownfield
are added we can use that package without additional effort.
- Add
ReactNativeActivity
toAndroidManifest.xml
file
1 |
<activity android:name="com.callstack.reactnativebrownfield.ReactNativeActivity"/> |
- Initialize react-native app with context as a first parameter (
this
in the snippet below)
1 2 3 4 5 6 |
List<ReactPackage> packages = new PackageList(getApplication()).getPackages(); HashMap<String, Object> options = new HashMap<>(); options.put("packages", packages); options.put("mainModuleName", "example/index"); ReactNativeBrownfield.initialize(this, options); |
- Start react-native app
1 2 3 |
ReactNativeBrownfield.getShared().startReactNative(init -> { Log.d("RN", "react-native app has started :)"); }); |
- Create
ReactNativeActivity
Intent withcontext
andmoduleName
as parameters and star activity.
1 2 3 4 5 |
Intent intent = ReactNativeActivity.createReactActivityIntent( this, "MainComponent" ); startActivity(intent); |
And voilà! We are ready to go 🙂 We don’t have to worry about passing activity lifecycles, back button events or native module that enable to back from react-native to native screen. All of those things are handled by react-native-brownfield
package.
Conclusion:
Lastly, 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