How to Integrate an Existing Android App with React Native
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.
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.
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.
{ "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);
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'
}
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.
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")
}
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"
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.
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
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.
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.
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
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.NavigationModuleclass 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.
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.