How to Integrate an Existing Android App with React Native

Danial
Level Up Coding
Published in
9 min readMay 24, 2020

--

In this guide, I will be going through how to integrate an existing Android application with React Native. This might be useful if you already have an existing Android application and would like to use React Native onwards, whilst still keeping some Android modules that you have already created.

React Native has the documentation on how to integrate it but it did not really fit my use case. React Native docs.

There might be issues with the auto-linking feature if you ever face them.

The code for this project can be found https://github.com/Danial968/React-Native-Android-Integration.

The use case that I will be tackling is when someone wants to start up a React Native app and go to their older Android modules.

Hybrid React Native and Android App

The Native App

I currently have a simple android application that has 2 activities and a single button which leads to the second activity. I will be integrating this application with React Native.

Native App

Package.json

Firstly, create a folder named “android” and place your existing native application within that folder.

Now create a ‘package.json’ within the root folder (not inside the android folder you just created). Edit the file accordingly to your use case.

package.json file
{    "name": "React Native",    "version": "0.0.1",    "private": true,    "scripts": {        "start": "npx react-native start",        "android": "npx react-native run-android"    }}

Once you have created the ‘package.json’ file open up a terminal and run the following commands. These will install the react-native dependencies and the other dependencies we will need to run the application.

npm add react-native
npm add react
npm add hermesvm
npm add jsc-android

build.gradle

Now open android studio and go into your application’s ‘build.gradle’. Locate the dependencies section.

Above the dependencies section, add the following lines of code.

project.ext.react = [
entryFile: "index.js" ,
enableHermes: false,
]

def jscFlavor = 'org.webkit:android-jsc:+'
def enableHermes = project.ext.react.get("enableHermes", false);
Above the dependencies section.

Within the dependencies section, add the following lines of code.

implementation 'com.android.support:appcompat-v7:28.0.0'
implementation "com.facebook.react:react-native:+" // React Native

if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";

debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
}
else { implementation jscFlavor }


debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}

debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}

debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
Code to add in the dependencies section.

Lastly at the bottom of the same ‘build.gradle’ file add this line of code

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

Now go to the ‘build.gradle’ file within the project folder. It usually has (Project) beside the gradle file in the gradle scripts.

build.gradle with (Project)

Locate the “allprojects” section and add the following lines of code. Make sure the code is above all other maven repositories.

maven {
// All of React Native (JS, Android binaries) is installed from npm
url ("$rootDir/../node_modules/react-native/android")
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
Local React Native maven directory.

Define your FLIPPER_VERSION in gradle.properties

FLIPPER_VERSION=0.33.1

Go to settings.gradle and add the following line of code to allow auto-linking

apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)

Sync your gradle changes.

AndroidManifest.xml

Go to AndroidManifest.xml to allow internet permission.

<uses-permission android:name="android.permission.INTERNET" />

To add DevSettingsActivity.

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

Apply Cleartext Traffic. Starting from API level 28 it is disabled by default and this will prevent you from connecting to your Metro bundler.

android:usesCleartextTraffic="${clearText}"  tools:targetApi="28"
AndroidManifest.xml

MainApplication

Now create a ‘MainApplication.java’ file within your android application. Paste the code below.

import android.app.Application;
import android.content.Context;

import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;

import java.lang.reflect.InvocationTargetException;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}

@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}

@Override
protected String getJSMainModuleName() {
return "index";
}
};

@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}

@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("sg.gov.tech.onemobileapp.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}

MainActivity

Now you will have to create a ‘MainActivity.java’. If you already have one I would suggest renaming it.

Once you have created the ‘MainActivity.java’, paste the code below. This extends the ReactActivity and it searches for the React Native component to start upon when this class is called in the ‘AndroidManifest.xml’.

The return statement in get ‘MainComponentName()’ is the name of the React Native component that you will export in AppRegistry in your ‘index.js’ file that we will create later.

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

@Override
protected String getMainComponentName() { return "MainScreen"; } // Name of the RN component that was exported by AppRegistry.registercomponent()
}

In my case I want the React Native code to be at the start of the launch so I made sure the Activity for MainActivity is still the Launcher and Main.

AndroidManifest.xml MainActivity setting

Within the application section, add the following snippet.

android:name=".MainApplication"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" // This removes the header on the Android app
AndroidManifest.xml changing android:name and theme

ReactNativeFlipper

Create a folder called debug with the path android/app/src/java. Within the debug folder create an ‘AndroidManifest.xml’ with the following code.

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/><application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" /></manifest>

Create a java folder within the same folder of the AndroidManifest.xml and follow your package name.

Create a ReactNativeFlipper.java file within the folder.

File directory for the ReactNativeFlipper, might be different from yours compare it to your main application to find out.
MainActivity has the same file structure but is in the “main” folder instead of debug folder.
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;

public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);

client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new ReactFlipperPlugin());
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());

NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();

// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}

Index.js

Now we will create a simple React Native file named ‘index.js’.

import React from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Button,
NativeModules
} from 'react-native';


class MainScreen extends React.Component {
render() {

console.log('The React Native app is running')

return (
<View style={styles.container}>
<Text style={styles.hello}>Hello, this is a React Native page</Text>
</View>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
hello: {
fontSize: 20,
textAlign: 'center',
margin: 10
}
});

AppRegistry.registerComponent(
'MainScreen', // Name of the component for the Android side to pick up
() => MainScreen
);

Note that the AppRegistry.registerComponent registers the name MainScreen which is the same as the name in ‘MainActivity.java’.

Running the application

Run the application by going into the file directory and use the command npm run android. If this has some issues you can build the app in android studio and run npm run start to start the metro bundler separately.

React Native page upon start up

Now we are able to start our app with the React Native page as the entry point. However, we are still unable to call the native modules within our React Native code.

NavigationModule

Create a new class in your android folder called NavigationModule this class will extend ReactContextBaseJavaModule.

import android.content.Intent;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class NavigationModule extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactContext;
private Intent intent;

NavigationModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}

@NonNull
@Override
public String getName() {
return "NavigationModule";
} //The name of the component when it is called in the RN code

@ReactMethod
public void navigateToNative(){
ReactApplicationContext context = getReactApplicationContext();
intent = new Intent(context,App.class);

if (intent.resolveActivity(context.getPackageManager()) != null) {
intent.setFlags((Intent.FLAG_ACTIVITY_NEW_TASK));
context.startActivity(intent);
}
}
}

In the class, there are 2 methods. The first method getName() will refer to the name of the class when it is called in the React Native code. In this case it will be called NavigationModule.

The second method NavigateToNative() is a method I created and is a ReactMethod. For this case when the method is executed I will want to go to my App class which is my native android activity.

Navigation Package

Now create a new class called NavigationPackage.java which implements ReactPackage.

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class NavigationPackage implements ReactPackage {
private ReactApplicationContext reactContext;

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}

@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();

modules.add(new NavigationModule(reactContext)); // Add the module that you would like to call to from RN

return modules;
}

}

Add the modules that you would like to call from React Native within the createNativeModules method. For this case I am going to add the NavigationModule.

This package will then have to be added into the getPackages() function in ‘MainApplication.java’. The function is the bridge to the React Native code

MainApplication.java file

React Native

To call the methods I will first have to import NativeModules from react-native.

import { NativeModules } from 'react-native'
const Navigation = NavativeModules.Navigation

I can call the function that I created in ‘NavigationModule.java’ by putting it as a function from an onPress event.

import React from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Button,
NativeModules
} from 'react-native';

const Navigation = NativeModules.NavigationModule
class MainScreen extends React.Component {
render() {

console.log('The React Native app is running')

return (
<View style={styles.container}>
<Text style={styles.hello}>Hello, this is a React Native page</Text>
<Button title="Go to native page" onPress={() => Navigation.navigateToNative()} /> </View>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
hello: {
fontSize: 20,
textAlign: 'center',
margin: 10
}
});

AppRegistry.registerComponent(
'MainScreen', // Name of the component for the Android side to pick up
() => MainScreen
);

Run the App

Now the application should be able to call a native page from React Native.

React Native App

While this process took me several days of trial and error. I hope this article suffices in helping your project. If you are stuck you might want to create an empty React Native application and compare it with yours to see what you are missing from a basic React Native app.

--

--