Adding React Native to an Existing Flutter App

Maciej Jastrzębski
No items found.

In short

This article explores integrating React Native into an existing Flutter app, leveraging Flutter’s platform views to blend React Native components seamlessly.

Highlights include detailed integration steps for both Android and iOS, performance assessments with popular React Native libraries, and insights into the challenges and benefits of maintaining dual cross-platform runtimes.


This is a companion to a GitHub repository for Flutter & React Native POC. Please reference the repository for a complete and working solution.

Introduction

One of the primary use cases for React Native is integrating it into existing apps that were originally developed with native technologies like iOS or Android. While many developers prefer building React Native apps from scratch, numerous companies enhance their native apps with React Native. Examples include Meta, which uses React Native for Facebook, and Microsoft, which extends features in Windows and Office apps.

Today, we’ll explore whether we can use React Native to extend an existing Flutter app and how feasible this integration is. But first, let’s start with a few words about Flutter.

What is Flutter?

Flutter, developed by Google, is another cross-platform development technology targeting iOS, Android, and macOS. Unlike React Native, Flutter uses Dart and its own widget and rendering system. It can draw widgets that resemble native platform widgets but doesn't use platform views natively.

How to add React Native to Flutter

Despite not using platform views natively, Flutter provides an option to integrate with existing apps. This allows developers to leverage their existing native code and dependencies, enabling a seamless blend of native and Flutter components within the same application.

We will leverage Flutter’s capability to integrate existing native views to add React Native views, which are true platform views. React Native can seamlessly mix with platform views, either by containing them or being contained within them, offering great flexibility. It uses the native view hierarchy but replaces the native layout engines with its own Yoga layout engine for laying out the views.

Exploring Native Views in Flutter

To explore the basics of bridging Flutter to native, I started with the goal of embedding some basic platform views, such as UILabel on iOS and TextView on Android, to confirm Flutter's ability to display these views. During this process, I discovered that Flutter supports two techniques for embedding native views: Hybrid Composition and Texture Layer.

Hybrid Composition

In Hybrid Composition, platform views are rendered normally, while Flutter views are rendered to a texture. This allows for the greatest fidelity for native views but comes with some limitations on the Flutter side, such as performance issues and restrictions on certain Flutter transformations.

Texture Layer

The other technique, Texture Layer, renders the native views to a texture, which is then integrated into Flutter's regular rendering system. According to the documentation, this should maximize Flutter's performance, though it may introduce performance issues on the native side, particularly during rapid scrolling. It's worth noting that for iOS, only Hybrid Composition is possible, while on Android, both techniques are supported.

Integrating React Native Views

Using the tutorials on the Flutter website, I was able to verify that native views work correctly on both iOS and Android. My next step was a significant one: swapping the simple native views for React Native-controlled ones.

Brownfield setup

In order to display React Native views, I needed to add React Native in the project, in so called brownfield setup. To do that, I followed tutorials available on the React Native site and the excellent guides prepared by my Callstack colleague Tomasz Misiukiewicz (iOS guide, Android guide). I’ve started by integrating React Native in the version used by Tomasz in his guides, which was RN 0.71. This approach provided two benefits: I could rely on some existing setup steps, and the setup for RN 0.71 is somewhat simpler compared to RN 0.74.

I started with React Native 0.71 and gradually migrated to the current version, 0.74, moving through each minor version—0.72 and 0.73. The React Native Upgrade Helper was invaluable in this process, as it allowed me to see the configuration file changes in the React Native template between versions.

Overall, the Flutter project structure is somewhat similar to that of React Native, with separate folders for iOS and Android platforms. Each platform has a simple shell: a single ViewController and an AppDelegate or iOS, and a single Activity for Android. Each platform uses its native build system—Xcode Build for iOS and Gradle for Android. My task was to augment this structure with the React Native-specific setup.

Android

I started with Android and extended the typical Gradle configuration files to include React Native-specific settings. The files I modified were:

I’ve included links to the final version of the files for reference. Each file contains code comments indicating the added configuration.

Next, I’ve created a ReactView, a Flutter platform view hosting a ReactRootView

To add flexibility in rendering different React components, it accepts a moduleNam parameter, which corresponds to the name assigned to the React component when it is registered in the app registry.

This class is accompanied by ReactViewFactory responsible for instantiating ReactView instances for Flutter:

Next, I added the MainApplication class which held ReactNativeHost object responsible for loading and managing React Native runtime lifecycle:

Next, I added relevant lifecycle events of that ReactNativeHos object to the MainActivity class:

Finally, I’ve modified the AndroidManifest.xml configuration file to accommodate the React Native setup:

For React Native 0.71, the process went relatively smoothly, and I was soon able to announce on X that I had successfully embedded React Native inside a Flutter app. The upgrade process, however, was more complicated. I added the React Native Gradle setup to the existing Flutter configuration in a somewhat experimental manner, not fully comprehending the complexities of the Gradle configuration system.

The problematic step was transitioning from React Native 0.72 to 0.73, which required me to use a proper version of the Kotlin language. Otherwise, the React Native Gradle configuration in Kotlin returned rather cryptic errors.

Having overcome that obstacle, I paid closer attention to the exact versions required for the Kotlin language, Gradle wrapper, Android Gradle Plugin, and the minimum SDK required by the React Native setup. This newly acquired knowledge led to a smoother upgrade to React Native 0.74. Additionally, I gained a much better understanding of the Gradle build system in the process.

iOS

Regarding the iOS implementation, I began by integrating React Native 0.71 into the project. The initial step involved incorporating the CocoaPods package management system, as Flutter does not utilize this system by default. Fortunately, this process is well documented in the React Native documentation. The basic steps include:

  1. brew install cocoapods - installs CocoaPods (if not already installed)
  2. pod init - initialize CocoaPods
  3. Copy the relevant React Native Podfile contents into your project’s Podfile
  4. pod instal - instant relevant pods

From that point on, you should open the xcworkspace file in Xcode instead of the xcodeproj. While this step may sound daunting, it went smoothly in my case without causing any issues.

Next, I created Flutter's platform view, which I called FLReactView to host React's RCTRootView along with a related view factory. This was based on my previous implementation using native views, with the addition of React Native-specific code. 

To add flexibility in rendering different React components, my FLReactView accepts a moduleName parameter, which corresponds to the name assigned to the React component when it is registered in the app registry.

Then, I added a BridgeManager class. It encapsulates bundle loading logic and holding of RCTBridge instance. I borrowed this idea from Tomek Misiukiewicz’s brownfield repository.

Finally, I’ve modified the AppDelegate to configure Flutter to handle FLReactViewas well as trigger loading React Native by BridgeManager.

Testing the limits

I was particularly interested in exploring the potential limitations of this solution, so I added a few popular React Native libraries to the project. I selected these libraries based on their popularity and internal complexity, as they were likely to reveal any issues.

1. Reanimated

Firstly, I integrated Reanimated, a well-known animations library for React Native. The setup was relatively straightforward and followed official instructions. However, this prompted me to adhere more closely to the React Native template, as my gradle.properties file was missing the hermesEnabled and newArchEnabled flags. I validated the setup by running some basic animations, which worked perfectly fine.

2. React Navigation

Next, I integrated React Navigation, a popular navigation library for React Native that relies on React Native Screens for platform navigation primitives. To avoid conflicts with Flutter's navigation bars, I adjusted my React Native setup so that React Navigation flows would be presented in a separate View Controller in iOS and separate Activity on Android. This basic setup works correctly but lacks back gesture and back button management.

3. React Native WebView

Finally, I added the React Native WebView component to display web pages. After all the previous tweaks and improvements, this component worked straight out of the box without any additional configuration.

The perceived performance of the solution was excellent. I did not notice any lag during animations or while scrolling through longer screens. Furthermore, there was no noticeable difference between the two Flutter native views rendering modes ( Hybrid Composition and Texture Layer); both worked equally well.

The presented solution is not completed yet. A few areas that could be further improved:

  • add support for React Native new architecture
  • improve integration between React Native and Flutter navigation

Summary

Wrapping things up, you can certainly add React Native to an existing Flutter app. The overall process is quite similar to integrating React Native into a native app, with the additional step of using Flutter’s platform views to display React-based views within the Flutter rendering system. I successfully integrated popular React Native libraries such as Reanimated, React Navigation, and React Native WebView, with the perceived performance being comparable to a standard React Native app.

The major drawback of such a solution is that your app would host two cross-platform runtimes, Flutter and React Native, simultaneously. This dual-runtime setup results in a larger app size and increased memory consumption

Why would you want to create a similar setup in real life? There are two primary use cases:

  1. to extend an existing Flutter app with new modules written in React Native
  2. to gradually migrate an existing Flutter app to React Native.

This approach allows you to progressively re-write entire screens, flows, and individual views to React Native over an extended period while maintaining the continuity of the production app.

References

Latest update:
July 24, 2024

FAQ

No items found.
React Galaxy City
Get our newsletter

By subscribing to the newsletter, you give us consent to use your email address to deliver curated content. We will process your email address until you unsubscribe or otherwise object to the processing of your personal data for marketing purposes. You can unsubscribe or exercise other privacy rights at any time. For details, visit our Privacy Policy.

Callstack astronaut
Download our ebook

I agree to receive electronic communications By checking any of the boxes, you give us consent to use your email address for our direct marketing purposes, including the latest tech & biz updates. We will process your email address and names (if you have entered them into the above form) until you withdraw your consent to the processing of your names, or unsubscribe, or otherwise object to the processing of your personal data for marketing purposes. You can unsubscribe or exercise other privacy rights at any time. For details, visit our Privacy Policy.

By pressing the “Download” button, you give us consent to use your email address to send you a copy of the Ultimate Guide to React Native Optimization.