blog content
Many teams that have made native apps are starting to add React Native to their existing codebase. There are many reasons behind it - the most important being probably the ability to develop features with one code for multiple platforms.
However, to make the most of it, developers should aim at the latest React Native version possible to leverage the New Architecture. In terms of brownfield development, it might be a little bit tricky, though. Let’s see how to enable it in brownfield apps and what we can do to make it easier.
Enabling the New Architecture in a brownfield app
In general, when integrating React Native in an iOS native app, to turn it into a brownfield app you should use <rte-code>RCTRootView<rte-code>. It’s a <rte-code>UIView<rte-code> that holds a React Native root, providing the interface between the hosted app and the native side at the same time. Let’s say we have an <rte-code>RCTRootView<rte-code> in our brownfield app, and we initialize it using <rte-code>initWithBundleURL<rte-code>:
<rte-code>initWithBundleUrl<rte-code> is intended to be used with the app as a single <rte-code>RCTRootView<rte-code>, and the bridge is created internally.
However, if you want to switch to the New Architecture in a brownfield app and use Fabric as a renderer, it takes a few more steps as you need to refactor the code on the app side. With Fabric enabled, you should move from <rte-code>RCTRootView<rte-code> to <rte-code>RCTFabricSurfaceHostingProxyRootView<rte-code>, or eventually use a proper renderer based on the <rte-code>RN_FABRIC_ENABLED<rte-code> value. Here’s what it looks like in the RNTester app’s AppDelegate:
As you might notice, enabling Fabric introduces some new classes that need to be used in order to run New Architecture. First of all, you need to create a bridge adapter using <rte-code>RCTSurfacePresenterBridgeAdapter<rte-code>, which controls the lifecycle of a Surface Presenter based on the bridge's lifecycle. A Surface Presenter coordinates presenting React Native surfaces. Since React Native is moving away from using the bridge, this class is intended to be used only during the transition period. Then you need to use <rte-code>RCTFabricSurfaceHostingProxyRootView<rte-code>, which is a Fabric-compatible implementation of <rte-code>RCTSurfaceHostingProxyRootView<rte-code>. In the future, <rte-code>RCTRootView<rte-code> will be deprecated in favor of <rte-code>RCTSurfaceHostingView<rte-code>.
This is a great example showing that the New Architecture is still experimental and implementation details might change. Enabling the New Architecture currently contains several manual steps. The moment it’s stable, the implementation can be completely different from what it is right now. If you want to start playing around with it now, you may have to refactor your code in the future.
You also need to keep in mind that React Native should be at least in version 0.68, but we highly recommend upgrading to at least 0.71, since a lot of improvements have been added there. You have to remember that all the third-party libraries your app depends on must be migrated to the New Architecture.
To simplify that process, Microsoft has introduced a helpful package which initially was part of react-native-test-app, but now it’s available as a standalone in rnx-kit (it’s a Microsoft monorepo where they host and maintain this React Native tooling). It’s designed to streamline React Native initialization and make it more flexible and compatible across different React Native versions. Let’s take a deeper look at <rte-code>react-native-host<rte-code>.
What is react-native-host?
React Native Host is a package created by Microsoft with the purpose of simplifying the React Native initialization. It provides a backward and forward-compatible way to initialize React Native, regardless of whether you use the New Architecture or have adopted a fully bridgeless approach.
When working with brownfield apps, developers often face the challenge of configuring the React Native root setup correctly. The ecosystem constantly evolves, and new architectural improvements are introduced over time. This can lead to confusion and difficulties in maintaining compatibility across different versions.
With <rte-code>react-native-host<rte-code>, Microsoft seeks to alleviate these pain points by providing a unified and simplified way to handle React Native initialization. A great example is <rte-code>react-native-test-app<rte-code>. It provides a sandbox app that easily allows you to test your code against multiple React Native versions and platforms, regardless of whether the New Architecture is used or not.
How does it apply to brownfield development? You can use this package to make your integration smoother. Most importantly, it’s easier to upgrade or move to the New Architecture.
At the moment of writing this article, it is available to use only for iOS and macOS, so we’ll focus on this part. However, there is a plan for Android and other platforms’ implementations - follow the rnx-kit repository if you want to stay up to date.
How does react-native-host work?
First of all, let’s assume that you already have an iOS-based React Native brownfield app. If integrating React Native with existing apps is something new for you and you want to learn more about it, the official guide is a good place to start.
You need to install <rte-code>react-native-host<rte-code> as a dev dependency in your project:
Autolinking should make the module available to the project. After running <rte-code>pod install<rte-code>, you should make a few additional changes (if they apply to your project):
- replace instances of <rte-code>RCTBridgeDelegate<rte-code> with <rte-code>RNXHostConfig<rte-code>. It allows you to customize the behavior of the JS bridge that is passing data between JavaScript and the native code. The <rte-code>RNXHostConfig<rte-code> is a superset, and it’s backward-compatible. It extends the bridge delegate with a few useful methods and values:
- <rte-code>isDevLoadingViewEnabled<rte-code> (boolean) - returns whether the loading view should be visible while loading JS
- <rte-code>shouldReleaseBridgeWhenBackgrounded<rte-code> - returns whether the bridge should be released when the app is backgrounded
- <rte-code>onFatalError<rte-code> - handles a fatal error
- replace instantiation of <rte-code>RCTBridge<rte-code> with <rte-code>ReactNativeHost<rte-code>. It will instantiate the appropriate modules required for your setup. It will also handle new architecture as necessary
- use host’s <rte-code>viewWithModuleName<rte-code> instead of <rte-code>RCTRootView<rte-code> to create your root views.
If you want to easily switch to New Architecture with React Native Host, all you need to do is to run <rte-code>RCT_NEW_ARCH_ENABLED=1 pod install<rte-code>. Under the hood, it automatically sets <rte-code>USE_FABRIC<rte-code> and <rte-code>USE_TURBOMODULE<rte-code> to <rte-code>1<rte-code>.
React Native Host is using <rte-code>RNXFabricAdapter<rte-code>, which is responsible for handling the installation of the surface presenter from the bridge adapter. It means that you don’t have to do this on your own when you want to run your brownfield app with the New Architecture.
It also takes care of using Fabric renderer automatically, so there is no need to define all the stuff you saw previously in the code snippet from RN Tester app. All it takes is using <rte-code>viewWithModuleName<rte-code> from <rte-code>ReactNativeHost<rte-code>, which is handling fabric internally:
To have a better understanding of how React Native Host simplifies integrating React Native into existing apps, just see the graph below. All the things that need to be handled when trying to run the New Architecture are handled internally just by React Native Host.
Let’s now move to some real-life examples and see how <rte-code>react-native-host<rte-code> can be used with an example brownfield app and how it simplifies moving to the New Architecture in that case.
Example usage
As an example, let’s use a brownfield app written in Swift (see the app’s repository). One of the screens contains a button that navigates to the React Native component.
The app contains a simple BridgeManager class responsible for creating and storing the instance of a bridge:
It loads JS bundle and instantiates the bridge in AppDelegate:
It also contains the ViewController responsible for rendering React Native view:
The function attached to the button simply navigates to the ViewController containing the React Native screen:
To begin with <rte-code>react-native-host<rte-code>, start with running <rte-code>yarn add @rnx-kit/react-native-host –dev<rte-code> and then <rte-code>pod install<rte-code> in your iOS folder. Once you are done with these steps, you are ready to make all the mentioned earlier changes required to start using React Native Host. First of all, let’s update the BridgeManager class to reflect those changes:
And then it’s a matter of making this small change in the ReactNativeViewController:
When you build your app again, your React Native component should be now initialized using React Native Host!
But how to quickly test it against the New Architecture? All it takes is running <rte-code>RCT_NEW_ARCH_ENABLED=1 pod install<rte-code> and building the app again. If everything goes fine, you should see Fabric enabled in your console after initializing React component:
Another important aspect of moving to this library is that, since it covers both Old and New Architecture, you can start with migrating to <rte-code>react-native-host<rte-code> and then upgrading your React Native version in a much smoother way.
Once it’s done, you can try moving to the New Architecture again by using <rte-code>RCT_NEW_ARCH_ENABLED<rte-code> flag. It’s very convenient in terms of improving the upgrade experience once the New Architecture is stable - your codebase should be ready to use without any additional effort.
Other capabilities
With <rte-code>ReactInstance<rte-code> as a class conforming <rte-code>RNXHostConfig<rte-code> protocol, you can easily extend it with some other <rte-code>react-native-host<rte-code> capabilities.
By creating the <rte-code>onFatalError<rte-code> function, you can easily handle errors:
React Native Host allows you to release the bridge once the app goes to the background. The great thing about it is that it will happen only when there are no React screens open. It reduces the probability of having consequences that would affect user experience (UX) when going back to the foreground.
This approach helps you free some resources, but you still need to keep in mind you should use this technique with caution. Shutting down the bridge might be a good idea when your brownfield app contains only a few screens created with React Native and the user does not enter them very often.
If you need to release the bridge once the app goes to the background, you can set a <rte-code>shouldReleaseBridgeWhenBackgrounded<rte-code> to true. This might be very helpful if you want to free some resources on the devices:
You can also shut down the bridge whenever you want by using the shutdown method from the host.
Sometimes you might need to call the specific block when the desired native module is retrieved. To achieve it, you can utilize the <rte-code>using<rte-code> method. Note that this may initialize the module.
Conclusions
React Native Host is a valuable tool that simplifies the process of integrating React Native with your existing iOS or macOS app. By adopting React Native Host, you can make your migration to the New Architecture easier and streamline the upgrading experience of React Native in your brownfield apps.
With its backward and forward-compatible approach, you can confidently integrate the latest React Native updates without compromising on the existing functionality. So, if you're considering integrating React Native into your existing app, don't hesitate to explore the benefits of using React Native Host to create a seamless and robust mobile application.