In this post I show you how it is possible to use multiple RCTRootView instances in an existing iOS app.
If we want to start to use React Native in an existing app, it’s really easy. We can have our first React Native component live inside our app by just following the getting started tutorial for existing app. But what happen if we need to use multiple react native component in different parts of our existing apps ? In this tutorial I will show you how we can use multiple instances of
RCTRootView
to show different React Native components in different parts of your app. Consider, for example, a simple iOS existing app with React Native. It has two very simple React Native components:
BlueScreen
, that shows a blue viewRedScreen
, that shows a red viewclass BlueScreen extends React.Component {
render() {
return (
<View style={styles.blue} />
);
}
}
class RedScreen extends React.Component {
render() {
return (
<View style={styles.red} />
);
}
}
const styles = StyleSheet.create({
blue: {
backgroundColor: "#0000FF",
width: "100%",
height: "100%"
},
red: {
backgroundColor: "#FF0000",
width: "100%",
height: "100%"
}
});
AppRegistry.registerComponent('BlueScreen', () => BlueScreen);
AppRegistry.registerComponent('RedScreen', () => RedScreen);
On the native side there’s a controller, ReactViewController
, that shows a React Native component given its name.
class ReactViewController: UIViewController {
init(moduleName: String) {
super.init(nibName: nil, bundle: nil)
view = RCTRootView(bundleURL: URL(string: "http://localhost:8081/index.bundle?platform=ios"),
moduleName: moduleName,
initialProperties: nil,
launchOptions: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
There’s also another controller, MainViewController
, that shows the React Native components described above using multiple instances of the ReactViewController
. The UI of the app is very simple: there are two buttons on the view of the MainViewController
. A tap on the first one shows the ReactViewController
with a RCTRootView
that contains the RedComponent
. A tap on the second one shows the ReactViewController
with a RCTRootView
that contains the BlueComponent
.
This basically means that in this app there are multiple RCTRootView
, one for each controller created. This instances are kept alive at the same time (because the MainViewController
keeps a reference to the two ReactViewController
). The code to start the React Native components is the same contained in the getting started tutorial for existing app.
class MainViewController: UIViewController {
private let blueViewController: ReactViewController
private let redViewController: ReactViewController
required init?(coder aDecoder: NSCoder) {
blueViewController = ReactViewController(moduleName: "BlueScreen")
redViewController = ReactViewController(moduleName: "RedScreen")
super.init(coder: aDecoder)
}
@IBAction func showRedScreen(_ sender: Any) {
navigationController?.pushViewController(redViewController, animated: true)
}
@IBAction func showBlueScreen(_ sender: Any) {
navigationController?.pushViewController(blueViewController, animated: true)
}
}
If we try to run the app something very strange will happen:
What’s happening here? Well, there’s something wrong in our code. If we take a look at the comments in the code of React Native for the RCTRootView
initializer, we will notice something very strange:
/**
* - Designated initializer -
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;
/**
* - Convenience initializer -
* A bridge will be created internally.
* This initializer is intended to be used when the app has a single RCTRootView,
* otherwise create an `RCTBridge` and pass it in via `initWithBridge:moduleName:`
* to all the instances.
*/
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions;
What ?????!?!?!??? This basically means that the documentation in the getting started is considering only the case where we will have a single
RCTRootView
instance. So we need to do something to our ReactViewController
so that we can keep multiple RCTRootView
alive at the same time.
The solution to our problem is contained in the comments of the initializer above: we need to use the designated RCTRootView
initializer to start to use multiple instances of them at the same time in the app. So the new ReactViewController
with the new RCTRootView
initialization is the following one:
class ReactViewController: UIViewController {
init(moduleName: String, bridge: RCTBridge) {
super.init(nibName: nil, bundle: nil)
view = RCTRootView(bridge: bridge,
moduleName: moduleName,
initialProperties: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Where do we get an instance of RCTBridge
for the new init of the ReactViewController
and RCTRootView
? A new object, ReactNativeBridge
, creates a new RCTBridge
instance and store it as a property.
The RCTBridge
instance needs a RCTBridgeDelegate
. Another new object, ReactNativeBridgeDelegate
, will be the delegate of the RCTBridge
.
class ReactNativeBridge {
let bridge: RCTBridge
init() {
bridge = RCTBridge(delegate: ReactNativeBridgeDelegate(), launchOptions: nil)
}
}
class ReactNativeBridgeDelegate: NSObject, RCTBridgeDelegate {
func sourceURL(for bridge: RCTBridge!) -> URL! {
return URL(string: "http://localhost:8081/index.bundle?platform=ios")
}
}
Now it is possible to modify the MainViewController
. This controller will create a single ReactNativeBridge
with a single RCTBridge
instance. This instance will be passed to the two ReactViewController
. So they will basically share the same bridge instance.
class MainViewController: UIViewController {
private let blueViewController: ReactViewController
private let redViewController: ReactViewController
private let reactNativeBridge: ReactNativeBridge
required init?(coder aDecoder: NSCoder) {
reactNativeBridge = ReactNativeBridge()
blueViewController = ReactViewController(moduleName: "BlueScreen",
bridge: reactNativeBridge.bridge)
redViewController = ReactViewController(moduleName: "RedScreen",
bridge: reactNativeBridge.bridge)
super.init(coder: aDecoder)
}
@IBAction func showRedScreen(_ sender: Any) {
navigationController?.pushViewController(redViewController, animated: true)
}
@IBAction func showBlueScreen(_ sender: Any) {
navigationController?.pushViewController(blueViewController, animated: true)
}
}
Now if we try to run the app again everything will work as expected:
The entire source code of the app used as example for this post is contained in this github repo.
Now we’re ready to use multiple React Native components at the same time in our app .
I recently discovered MockK, a mocking library created for Kotlin. Let’s see how it is possible to write modern unit tests with MockK + JUnit 5.
Read MoreDuring the last months I worked a lot with Spring Boot backend applications. In this post I explain how you can consume a REST api from a Spring Boot application using RestTemplate and (the new) WebClient.
Read MoreRecently I upgraded my ID3TagEditor swift package to the latest Swift tools version (5.3). During the upgraded I discovered that now you can bundle reources with your Swift package. In this post I will show you how you can do this, and also a interesting trick in order to be able to build a project as a Swift Package and as a standard project from Xcode.
Read More