How to Integrate multiple applications with React Native
Hello Guys, This tutorial will guide you on how to integrate multiple apps with React Native. I’ve applied it to a number of projects. Hope I can help you
There are many ways to integrate multiple apps together, with React Native
For example:
- Integrate an existing application (native ANDROID)
- Integrate an existing application (native IOS)
I will guide you to integrate React Native app with React Native
Table of Content
- Ingredients
- Create two mini-apps to connect with the super app
- How to setup a super application to connect multiple mini apps
- How to pass props
Ingredients
Here is a quick summary of my setup:
- Android Studio ( Electric Eel )
- XCode ( v14.2 )
- ITerm
- Nvim ( My Setup customized by Takuya )
- Nodejs ( v16.14.2 )
- Yarn ( v1.22.19 )
- Npm ( v8.5.0 )
Setup a SuperApp
Need a Superapp to connect multiple applications:
npx react-native init SuperApp - template react-native-template-typescript
- Create folder assets in Android to save bundle files of the mini app
cd $ROOT_PROJECT/android/app/src/main
mkdir assets
Create mini apps
I will create two mini-apps to connect with the super app
Go to the root directory of the super app
Create folder $ROOT_PROJECT/miniapp/*
CMD in $ROOT_PROJECT super app:
mkdir miniapp
cd miniapp
npx react-native init MiniAppOne - template react-native-template-typescript
- Project one will create with the name
MiniAppOne
in the folder mini app
Edit file App.tsx
from MiniAppOne
:
import React from 'react';
import {Text, SafeAreaView, StyleSheet} from 'react-native';
const App = (props: any): JSX.Element => {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>App One</Text>
<Text style={styles.content}>
Here props from super app: {JSON.stringify(props)}
</Text>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#ffffff',
flex: 1,
},
title: {
fontSize: 24,
color: 'red',
fontWeight: 'bold',
},
content: {
fontSize: 16,
color: 'blue',
},
});
export default App;
Run application:
Generate a file bundle of
MiniAppOne
with:
npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output ../../android/app/src/main/assets/index.android-1.bundle --assets-dest ../../android/app/src/main/res/
Project two name is
MiniAppTwo
in the folder mini app
npx react-native init MiniAppTwo - template react-native-template-typescript
Same with MiniappOne
Edit file App.tsx
from MiniAppTwo
:
import React from 'react';
import {Text, SafeAreaView, StyleSheet} from 'react-native';
const App = (props: any): JSX.Element => {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>App Two</Text>
<Text style={styles.content}>
Here props from super app: {JSON.stringify(props)}
</Text>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#ffffff',
flex: 1,
},
title: {
fontSize: 24,
color: 'red',
fontWeight: 'bold',
},
content: {
fontSize: 16,
color: 'blue',
},
});
export default App;
Run application:
Generate a file bundle of
MiniAppTwo
with:
npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output ../../android/app/src/main/assets/index.android-2.bundle --assets-dest ../../android/app/src/main/res/
Now, we will start building SUPERAPP to connect these 2 MINIAPPs together
This is what we have prepared for SUPERAPP:
ON THE ANDROID SUPER APP
In folder $ROOT_PROJECT/android/app/src/main/java/com/superapp/*
- Create a folder
miniapp
, which includes three filesConnectNativeModule.java
,ConnectNativePackage.java
to operate mini-apps, andMiniAppActivity.java
to handle multiple activities of Android - On the
AndroidManifest.xml
. You make added the line:
<activity
android:name=".miniapp.MiniAppActivity"
android:windowSoftInputMode="adjustResize"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:exported="false" />
To the super app can understand Miniapps Activity.
In the file MiniAppActivity.java
: In the activity section, I have overridden the functionality of the physical back button on Android because it could potentially affect the mini apps.
public class MiniAppActivity extends ReactActivity implements DefaultHardwareBackBtnHandler {
private static MiniAppActivity mInstance;
private String mMainComponentName;
private static ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInstance = this;
Bundle bundle = getIntent().getExtras();
assert bundle != null;
mMainComponentName = bundle.getString("bundleName", "");
boolean devLoad = bundle.getBoolean("devLoad");
Bundle initProps = bundle.getBundle("initProps");
ReactRootView mReactRootView = new ReactRootView(this);
// val packages: List<ReactPackage> = PackageList(application).packages
String appPath = bundle.getString("appPath", "");
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setJavaScriptExecutorFactory(new HermesExecutorFactory())
.setCurrentActivity(this)
.setBundleAssetName(appPath)
.setJSMainModulePath("index")
.addPackages(getPackages())
.setUseDeveloperSupport(devLoad)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
mReactRootView.startReactApplication(mReactInstanceManager, mMainComponentName, initProps);
setContentView(mReactRootView);
}
public ArrayList<ReactPackage> getPackages() {
return new ArrayList<>(Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ConnectNativePackage()
));
}
@Override
protected String getMainComponentName() {
return mMainComponentName;
}
public static void close() {
if (mInstance != null) mInstance.finish();
mInstance = null;
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
}
In the file ConnectNativeModule.java
:
public class ConnectNativeModule extends ReactContextBaseJavaModule {
private ReactApplicationContext reactContext;
private static final Map<String, ReactContext> _reactContexts = new HashMap<>();
private static Callback _closeCallback = null;
public ConnectNativeModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@ReactMethod
public void openApp(String bundleName, String appPath, ReadableMap initProps,
boolean devLoad, Callback callback) {
Intent intent = new Intent(reactContext, MiniAppActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Bundle bundle = new Bundle();
bundle.putString("bundleName", bundleName);
bundle.putString("appPath", appPath);
bundle.putBoolean("devLoad", devLoad);
bundle.putBundle("initProps", Arguments.toBundle(initProps));
intent.putExtras(bundle);
reactContext.startActivity(intent);
addBridge(bundleName);
_closeCallback = callback;
}
@ReactMethod
public void sendMessage(String bundleName, ReadableMap msg, Callback callback) {
ReactContext reactContext = _reactContexts.get(bundleName);
WritableMap result = Arguments.createMap();
if (reactContext != null) {
WritableMap map = Arguments.createMap();
map.merge(msg);
pushEvent(reactContext, "EventMessage", map);
result.putString("msg", "Send message ok!");
} else {
result.putString("msg", "Cannot find this bundle name " + bundleName);
}
callback.invoke(result);
}
@ReactMethod
public void addBridge(String bundleName) {
_reactContexts.put(bundleName, reactContext);
}
@ReactMethod
public void closeApp(String bundleName) {
if(_closeCallback == null) {
return;
}
MiniAppActivity.close();
_closeCallback = null;
_reactContexts.remove(bundleName);
}
@ReactMethod
public void getBundleNames(Promise promise) {
if (_reactContexts.keySet().toArray() != null) {
String[] bundleNames = Objects.requireNonNull(_reactContexts.keySet().toArray(new String[0]));
WritableArray arrays = Arguments.fromArray(bundleNames);
promise.resolve(arrays);
} else {
promise.reject(new Throwable("No listeners"));
}
}
private void pushEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
@Override
public String getName() {
return "ConnectNativeModule";
}
}
In the file ConnectNativePackage.java
:
public class ConnectNativePackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new ConnectNativeModule(reactContext));
}
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Okay, that’s fine. Now, we should add some interactions from the mini app to the super app.
I will make some modifications to mini app one and bundle it again. You can do the same for mini app two.
Edit file App.tsx
from MiniAppOne
:
import React from 'react';
import {
Text,
SafeAreaView,
StyleSheet,
TouchableOpacity,
NativeModules,
} from 'react-native';
import AppInfo from './app.json';
const {ConnectNativeModule} = NativeModules;
const App = (props: any): JSX.Element => {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>App One</Text>
<Text style={styles.content}>
Here props from super app: {JSON.stringify(props)}
</Text>
<TouchableOpacity
style={styles.button}
onPress={() => {
ConnectNativeModule?.closeApp(AppInfo.name);
}}>
<Text style={styles.content}>Close App</Text>
</TouchableOpacity>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#ffffff',
flex: 1,
},
title: {
fontSize: 24,
color: 'red',
fontWeight: 'bold',
},
content: {
fontSize: 16,
color: 'blue',
},
button: {
borderRadius: 4,
backgroundColor: 'green',
borderWidth: StyleSheet.hairlineWidth,
padding: 20,
margin: 20,
justifyContent: 'center',
alignItems: 'center',
},
});
export default App;
And here is my super app’s App.tsx
file.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
*/
import React, {useCallback, useState} from 'react';
import {
Keyboard,
KeyboardAvoidingView,
NativeModules,
Platform,
SafeAreaView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
TouchableWithoutFeedback,
View,
} from 'react-native';
const {ConnectNativeModule} = NativeModules;
interface App {
bundleId: string;
appName: string;
}
function App(): JSX.Element {
const [input, setInput] = useState<string>('');
const LIST_APPS: Array<App> = [
{
bundleId: `index.${Platform.OS}-1.bundle`,
appName: 'MiniAppOne',
},
{
appName: 'MiniAppTwo',
bundleId: `index.${Platform.OS}-2.bundle`,
},
];
const goToNextApp = useCallback(
async (item: App) => {
ConnectNativeModule?.openApp(
item.appName,
item.bundleId,
{
text: input,
},
false,
() => {},
);
const result = await ConnectNativeModule?.getBundleNames();
return result;
},
[input],
);
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
style={styles.container}
keyboardVerticalOffset={64}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<SafeAreaView style={styles.container}>
<Text style={styles.title}>Input send to miniapp</Text>
<TextInput
value={input}
placeholder="please input here"
onChangeText={text => setInput(text)}
style={styles.input}
/>
<View style={styles.content}>
{LIST_APPS.map(app => (
<TouchableOpacity
key={app?.bundleId}
style={styles.btnApp}
onPress={() => goToNextApp(app)}>
<Text style={styles.appName}>{app?.appName}</Text>
</TouchableOpacity>
))}
</View>
</SafeAreaView>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
alignItems: 'center',
justifyContent: 'center',
flex: 1,
},
appName: {
fontWeight: 'bold',
fontSize: 24,
color: '#fff',
},
btnApp: {
borderWidth: 1,
backgroundColor: '#999',
padding: 16,
borderRadius: 60,
},
input: {
borderRadius: 4,
borderWidth: StyleSheet.hairlineWidth,
marginHorizontal: 16,
marginVertical: 10,
},
title: {
fontWeight: 'bold',
fontSize: 20,
padding: 20,
},
});
export default App;
This is the result for Android.
Yeah, the article has become quite long now, and I believe that for iOS, it will be similar to what we wrote earlier. If you have any questions, feel free to contact me via email. I will try to respond. If there is support from you all, I will provide instructions for iOS as well. Thank you.
Related Post:
- CMD for bundle React native: https://fig.io/manual/react-native/bundle
- React Native:
— https://reactnative.dev/docs/integration-with-existing-apps
— https://reactnative.dev/docs/getting-started - Source: https://github.com/votrai123/integrate-multiple-react-native
- Part — 2: https://trai-nguyen.medium.com/how-to-integrate-multiple-applications-with-react-native-part-2-44b1ac4d6864
- Email: trainguyen1199@gmail.com