Introduction
While there’s plenty of theory surrounding brownfield development, practical examples are harder to find. To bridge this gap, we sought to carry out the integration in a practical setting, looking for open-source projects where we could apply brownfield migration to React Native. We chose the Mastodon app, given its availability and decentralized architecture, as our testing ground for integrating React Native into its mobile apps on both iOS and Android.
A Step-by-Step Guide to the React Native Integration
We aimed to understand how React Native 0.76 could be incorporated into the native iOS and Android apps, providing insights on how to deal with real-world integration challenges.
To explore the possibilities of brownfield integration with the latest React Native, we began by working on Mastodon’s open-source iOS app, written in Swift. The goal was to merge React Native with the native code without rebuilding the app entirely, ensuring that both platforms (iOS and Android) would be compatible with this cross-platform solution.
Fork and Set Up the Projects First
We started by creating a new repository and forking the Mastodon iOS and Android projects. The next step involved linking the fork with our React Native project using git submodules. This allowed us to manage the projects as a sub-repository.
Migrating a Brownfield iOS App to React Native
Step 1: Create package.json
In the root of the project, we need a package.json file. The easiest way to create the file is to follow the steps outlined in the official React Native documentation on integration with existing apps. This approach will help ensure the correct versions of React Native and React will be installed.
curl -O https://raw.githubusercontent.com/react-native-community/template/refs/heads/<version>-stable/template/package.json
Replace <react-native-version> with the version you want to integrate with your application and run the command.
Step 2: Modify the Podfile
If a Podfile doesn't exist in your iOS project, run pod init. Then, locate the appropriate Podfile for your React Native version and copy the necessary content into your existing one. This step ensures the proper linkage of React Native’s dependencies. Remember to adjust the minimum deployment target of your app according to the version from the React Native template. You can find the corresponding version in the React Native repository (min_ios_version_supported).
Step 3: Install Pods
Once the Podfile is updated, run pod install to install the required dependencies for React Native. With this done, you're ready to move forward with the integration.
Step 4: Add the React Native Component
Create an index.js file with a basic React Native component using AppRegistry.registerComponent. The registered component name will be used later for navigation.
Step 5: Modify AppDelegate.swift
Since iOS blocks HTTP resource loading by default, update the Info.plist file to allow communication with the Metro server. In AppDelegate.swift, start with inheriting from RCTAppDelegate and setting automaticallyLoadReactNativeWindow to false. It will instruct React Native that the app is handling the UIWindow. You should also override all the application functions.
The last step here is adding sourceURL and bundleURL methods used to tell React Native where it can find the JS bundle that needs to be rendered. Declare the window property to manage and display the React Native view.
Info.plist diff:
AppDelegate.swift:
Step 6: Create ReactNativeView class
Next, create a ReactNativeView class that uses RootViewFactory to create React Native views. You can read more about it in our dedicated article on brownfield integration with RootViewFactory.
Step 7: Link the Button to the React Native Component
Find the method triggered by pressing the “Join http://mastodon.social” button and replace its content with code to navigate to your React Native component. Make sure the module name matches the one registered in index.js.
Migrating a Brownfield Android App to React Native
With the iOS integration complete, the next step was to repeat the process for the Android version of the Mastodon app.
Step 1: Align the project’s build gradle with the React Native template
Let’s start with aligning the compileSdk, minSdk, ndk and buildToolsVersion to match the React Native template. To find out which versions you need, go to libs.versions.toml file in the React Native repository. We should also use autolinkLibrariesWithApp in app/build.gradle.
Step 2: Add React Native Gradle Plugin
In the top-level build.gradle, add the React Native Gradle Plugin dependency to make it available throughout the project.
Step 3: Modify settings.gradle
To configure dependencies and support React Native's autolinking feature, update settings.gradle by adding the React Native Gradle plugin. Sync the project with the Gradle files.
Step 4: Update AndroidManifest.xml
Ensure the AndroidManifest includes the required permissions for Internet access and Metro server communication. Add the usesCleartextTraffic attribute and the Dev Settings Activity. It’s also important to add a new Activity with a name that is equivalent to the class name you’ll create in the next step.
You also need to set the theme of MyReactActivity to Theme.AppCompat.Light.NoActionBar (or to any non-ActionBar theme) as otherwise your application will render an ActionBar on top of your React Native screen.
Step 5: Modify the MainApplication
Update the class to integrate React Native functionality by implementing the ReactApplication interface. You’ll also need to add a ReactNativeHost, which is responsible for managing the React Native instance lifecycle, along with a few additional methods. Additionally, pass OpenSourceMergedSoMapping as a second parameter to SoLoader.init() function. It’s needed because starting from 0.76, React Native introduces libreactnative.so, which merges multiple dynamic libraries into one library, reducing app size and startup time. If you are upgrading to lower version, you don’t have to do that.
Step 6: Create React Activity
Create a new ReactActivity class that hosts the React Native code. It’s responsible for starting a new React Native runtime and rendering the component. This activity should use the component name registered in your index.js file. After adding the activity to the AndroidManifest, sync the project with Gradle:
Step 7: Link the Button to the React Native Component
Similarly to iOS, find the method tied to the “Join http://mastodon.social” button and overwrite it to navigate to the React Native screen. This completes the integration on Android.
Step 8: Enable the New Architecture
To enable New Architecture, add the following lines to the gradle.properties file:
Run the App on iOS and Android
To test the integration on both iOS and Android, start by running the Metro server using npm start in the project root.
For iOS, open the Mastodon.xcworkspace file in Xcode and build the app. If everything is set up correctly, you should be able to navigate to the React Native screen. You’ll be able to switch between screens and access the Dev Menu for debugging.
For Android, open the app in Android Studio and run it from there. If properly configured, the app should display the React Native screen and allow smooth transitions between native and React Native views on both platforms.
Conclusions
Integrating React Native into the Mastodon app offered valuable lessons in brownfield development. It demonstrated how React Native can modernize an app’s architecture while maintaining its core native performance, making it a practical solution for future scaling in both open-source and proprietary projects.