Building Cross-Platform React Native Bridges for iOS and Android
Connecting React Native with Native Code: A Deep Dive into Bridges
React Native's strength lies in its ability to bridge the gap between JavaScript and native platforms (iOS and Android). This "bridge" facilitates communication, enabling developers to leverage existing native libraries within their React Native applications. This capability is crucial for building production-ready apps with enhanced performance and access to platform-specific features. This article provides a comprehensive guide to creating native bridges that work seamlessly across both iOS and Android, allowing you to harness the power of Swift/Objective-C and Java within your React Native projects.
Building Our Example: The LightApp
To illustrate the concept, we'll build a simple "LightApp" using the react-native CLI. This app will demonstrate how to control a virtual "Bulb" through a native bridge. The same React code will function on both platforms, showcasing the cross-platform nature of this approach.
react-native init LightApp
cd LightApp
The tutorial is divided into two sections: Section 1: Native Bridge in iOS; Section 2: Native Bridge in Android.
Section 1: Native Bridge in iOS
This section focuses on building a bridge between Swift/Objective-C and your React Native component. The process involves three key steps: creating the Bulb class and bridge header, addressing GCD queue warnings, and accessing variables and implementing callbacks.
Step 1: Creating the Bulb Class and Bridge Header
First, within Xcode, create a new Swift file named Bulb.swift. Also, create a bridging header file (if one doesn't already exist) named LightApp-Bridging-Header.h. This header file allows communication between Swift and Objective-C. Add the following line to the bridging header:
#import <React/RCTBridgeModule.h>
This import provides the necessary interface for bridging.
Next, update Bulb.swift with the following code:
import Foundation
@objc(Bulb)
class Bulb: NSObject {
@objc static var isOn = false
@objc func turnOn() {
Bulb.isOn = true
print("Bulb is now ON")
}
}
We've created a Bulb class inheriting from NSObject, making it compatible with Objective-C. The @objc annotation exposes the class and the turnOn function to Objective-C and subsequently to React Native.
Now, update App.js to access the Bulb class:
import React, { Component } from 'react';
import { Text, View, NativeModules, TouchableOpacity, StyleSheet } from 'react-native';
export default class App extends Component {
turnOn = () => {
NativeModules.Bulb.turnOn();
};
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to Light App!!</Text>
<TouchableOpacity onPress={this.turnOn}>
<Text>Turn On</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});
Running the app and checking the Xcode console will confirm that the Swift turnOn method is called from JavaScript.
Step 2: Addressing GCD Queue Warnings
You might encounter warnings related to running native code on the wrong thread. To ensure the native module runs on the main queue, add the following to Bulb.swift:
@objc static func requiresMainQueueSetup() -> Bool {
return true
}
Step 3: Accessing Variables and Implementing Callbacks
To display the bulb's status in the app, add a getStatus function to Bulb.swift:
@objc func getStatus(_ callback: RCTResponseSenderBlock) {
callback([NSNull(), Bulb.isOn])
}
This function takes a callback and returns the isOn value. Update App.js to utilize this:
constructor(props) {
super(props);
this.state = { isOn: false };
this.updateStatus();
}
turnOn = () => {
NativeModules.Bulb.turnOn();
this.updateStatus();
};
updateStatus = () => {
NativeModules.Bulb.getStatus((error, isOn) => {
this.setState({ isOn });
});
};
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to Light App!!</Text>
<TouchableOpacity onPress={this.turnOn}>
<Text>Turn On</Text>
</TouchableOpacity>
<Text>Bulb is {this.state.isOn ? "ON" : "OFF"}</Text>
</View>
);
}
Section 2: Native Bridge in Android
Now, let's create the equivalent functionality for Android. Create a Java class named Bulb.java within the Android project and add the following code:
package com.lightapp; // Replace with your app's package name
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class Bulb extends ReactContextBaseJavaModule {
private static boolean isOn = false;
public Bulb(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "Bulb";
}
@ReactMethod
public void turnOn() {
isOn = true;
System.out.println("Bulb is turned ON");
}
@ReactMethod
public void getStatus(Callback successCallback) {
successCallback.invoke(null, isOn);
}
}
Next, create a Java class named BulbPackage.java and add the following code to register the module:
package com.lightapp; // Replace with your app's package name
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 BulbPackage implements ReactPackage {
@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 Bulb(reactContext));
return modules;
}
}
Finally, you need to register the BulbPackage in your MainApplication.java file. Import your BulbPackage class, and add it to the getPackages method:
// ... other imports
import com.lightapp.BulbPackage; // Import your BulbPackage
public class MainApplication extends Application implements ReactApplication {
// ... existing code ...
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackagesList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new BulbPackage()); // Add this line
return packages;
}
// ... existing code ...
}
The same JavaScript code used for iOS will work for Android as well, demonstrating the cross-platform compatibility of the bridge.
Conclusion
By following these steps, you can effectively create and utilize React Native bridges, unlocking the potential of native functionalities within your cross-platform applications. This allows you to build richer, more performant apps that leverage the best of both worlds. Remember to rebuild your app after making native code changes for them to take effect.
Defx, with its extensive experience in IT services, can help you navigate the complexities of React Native development and create robust, high-quality cross-platform applications.
Need this built?
DEFX ships react native work like this for teams worldwide.
Tell us what you're building and we'll reply within 1 business day — or grab a call slot directly.