Powering Native Apps with React Native Brownfield

There are many ways to develop React Native apps. One popular method is starting from scratch, creating an empty repository and building upon it. While ideal for new projects, this doesn't suit everyone, particularly teams with existing codebases and established workflows.

What is brownfield development?

In software engineering, brownfield development refers to the practice of building new systems or features within an existing software infrastructure. For React Native, "brownfield" describes integrating React Native components into established native Android and iOS applications.

Brownfield React Native development is inherently complex because:

  • Every decision impacts existing systems.
  • Communication between technologies cause runtime and development overhead.
  • Build systems need to handle two or more types of technologies.

To address the pain points and make React Native brownfield development more accessible, we've created a library called React Native Brownfield.

React Native Brownfield

Initially, React Native Brownfield only handled the navigation between native and React Native screens. However, with the 1.0.0 release, this has changed and we've added many new interesting features.

React Native Brownfield 1.0.0 is out! 🚀

- Better API 🔧
- Supports packaging as a framework 📦
- Supports SwiftUI, Jetpack Compose ✨

Check out the sample integration below ⬇️ pic.twitter.com/7Ju0SyewAo

— Oskar Kwaśniewski (@o_kwasniewski) April 16, 2025

Loading React Native components as native views

One of the biggest features of brownfield React Native applications is the ability to present React Native components in native screens. We've introduced a new view loader API to React Native Brownfield that can load React Native components as:

  • SwiftUI views
  • UIKit view controllers
  • UIKit views
  • Jetpack Compose views
  • Android fragments
  • Android views

The loaded components can operate either as standalone screens or as embedded widgets. The lifecycle management of these components is seamlessly handled by React Native Brownfield.

SwiftUI example

import SwiftUI
import ReactBrownfield
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Welcome to the Native App")
.padding()
NavigationLink("Push React Native Screen") {
ReactNativeView(moduleName: "Onboarding")
.navigationBarHidden(true)
}
}
}
}
}

UIKit example

import ReactBrownfield
import UIKit
MyViewController: UIViewController {
private let onboardingViewController = ReactNativeViewController(
moduleName: "Onboarding",
initialProperties: ["screenIndex": 3]
)
// ...
}

or as a view:

import ReactBrownfield
import UIKit
MyViewController: UIViewController {
private let onboardingView = ReactNativeView(
moduleName: "Onboarding",
initialProperties: ["screenIndex": 3]
)
// ...
}

Jetpack Compose example

import com.callstack.reactnativebrownfield.ReactNativeBrownfield
// ...
AndroidView(
factory = { context ->
ReactNativeBrownfield.shared.createView(context, fragmentActivity, "Onboarding")
},
modifier = Modifier.fillMaxSize()
)
view raw Onboarding.kt hosted with ❤ by GitHub

Android Views example

import com.callstack.reactnativebrownfield.ReactNativeFragment
// ...
map.putInt("screenIndex", 3)
ReactNativeFragment.createReactNativeFragment("Onboarding", map)
view raw Onboarding.kt hosted with ❤ by GitHub

or as a view:

val onboardingView = ReactNativeBrownfield.shared.createView(
context,
activity,
"Onboarding"
)
container.addView(onboardingView)
view raw Onboarding.kt hosted with ❤ by GitHub

Packaging: xcframework and aar support

Integrating React Native into native apps requires bundling JavaScript and native dependencies. Two primary strategies exist:

Single repository integration

In this approach, Android, iOS, and React Native code is hosted in a single repository. The folder structure often looks like this:

android/ // Native Android App
├─ build.gradle
ios/ // Native iOS App
├─ MyProject.xcodeproj/
src/ // React Native components
package.json
...
view raw gistfile1.txt hosted with ❤ by GitHub

This approach works well enough as a simpler integration. All the capabilities of React Native can be utilized. However, there are a few downsides, such as:

  • Android, iOS, and the React Native app teams have to work in a single repo.
  • The iOS app needs a Cocoapods integration.
  • The Android app needs to install React Native specific dependencies using Gradle.

Despite all the downsides, this integration is perfect for teams that need a simpler setup.

Separate repositories with native packages

A more sophisticated strategy involves separate repositories for each platform, where React Native is packaged into native-friendly formats like .xcframework and .aar files. This setup promotes encapsulation, simplifies developer environments, and keeps React Native specific tools isolated from native developers.

The resulting repos look like this:

theapp-android. // Android app repo that consumes the .aar file
theapp-ios. // iOS app repo that consumes the .xcframework file
theapp-react-native // React Native repo that generates the .xcframework and .aar files
view raw gistfile1.txt hosted with ❤ by GitHub
In addition to .xcframeworks, we also plan to support first-party integration with the Swift Package Manager.

This approach works perfectly for teams with more sophisticated setups. It promotes better encapsulation, and native app developers don't need to install JavaScript-specific tooling. However, implementing it requires a deep technical understanding of how React Native is architected.

With this release, we've introduced full support for the packaging approach. More content about the packaging approach will be released later.

Common entry patterns to initialize React Native

For brownfield apps, native code initiates the React Native runtime.

In brownfield React Native apps, the native code serves as the entry point and it's responsible for spawning the React Native screens. We've created flexible APIs for initialization:

iOS

AppDelegate

import UIKit
import ReactBrownfield
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
ReactNativeBrownfield.shared.startReactNative {
print("React Native bundle loaded")
}
return true
}
}

SwiftUI

import SwiftUI
import ReactBrownfield
@main
struct MyApp: App {
init() {
ReactNativeBrownfield.shared.startReactNative {
print("React Native bundle loaded")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
view raw MyApp.swift hosted with ❤ by GitHub

Android

Using a React Native Host Delegate

import com.callstack.reactnativebrownfield.ReactNativeBrownfield
val mReactNativeHost = object : ReactNativeHost(application) {
override fun getUseDeveloperSupport(): Boolean {
return BuildConfig.DEBUG
}
override fun getPackages(): List<ReactPackage> {
return listOf<ReactPackage>(PackageList(this).getPackages())
}
override fun getJSMainModuleName(): String {
return "index"
}
}
ReactNativeBrownfield.initialize(this, mReactNativeHost)
view raw MainActivity.kt hosted with ❤ by GitHub

Passing Options Directly

val packages = PackageList(this).getPackages()
val options = hashMapOf<String, Any>(
"packages" to packages,
"mainModuleName" to "example/index"
)
ReactNativeBrownfield.initialize(this, options)
view raw MainActivity.kt hosted with ❤ by GitHub

Based on our experience, we recommend using React Native Brownfield in the following scenarios as it's a perfect fit:

  • Gradually transitioning to React Native: Converting your entire app to React Native in one go may be impractical. A gradual transition, feature by feature, can reduce risk and cost.
  • Enabling web developers to build native apps: Web teams already familiar with React can seamlessly transition to developing mobile app features.
  • Replacing webviews: Native apps often use webviews for certain user interactions such as handling payments, managing user accounts, and support. However, many teams are not happy with the user experience of webviews. React Native screens are perfect replacements in such cases.
  • Sharing UI components across platforms: Common UI elements can easily be reused across Android, iOS, and web, streamlining maintenance and ensuring consistency.

Conclusion

Brownfield development with React Native lets you add React screens to existing native apps without starting from scratch. Teams can gradually extend their apps, involve new developers, and avoid the high risks of full rewrites. We built React Native Brownfield by capturing the lessons and best practices from real brownfield projects, making it a practical, battle-tested solution that simplifies integration, packaging, and flexibility in real-world apps.

‍

Insights

Learn more about Brownfield

cover

Aug 27Article

React Native in .NET MAUI: A Practical Brownfield Integration Guide

Learn how to embed React Native inside a .NET MAUI app with a brownfield setup. This guide covers runtime packaging, lifecycle wiring, navigation, and shared state across both runtimes.

cover

Aug 3Article

Migration to React Native in 2026 Starts With a Delivery Question

Choosing between brownfield and greenfield React Native migration? Learn where risk sits, when to use a checkpoint, and what to prove before committing.

cover

Jul 8Article

How we made Hybrid iOS apps compile 39% faster with React Native Brownfield

See how React Native Brownfield uses iOS prebuilts to cut packaging times by up to 39% for hybrid apps, with details on setup, internals, and benchmark results.

cover

Jun 18Article

How We Made the Brownfield Gradle Plugin 22% Faster

See how task-driven Gradle optimization made the Brownfield Gradle Plugin 22% faster for clean Android release builds.

cover

Jun 3Article

AI-Supported Brownfield Migration to React Native

See how AI-supported workflows cut brownfield React Native evaluation from weeks to days by speeding up setup, planning, implementation, and QA.

cover

May 18Article

Unlocking Expo Updates in an Isolated Brownfield Architecture with SDK 55

Learn how to enable Expo Updates in an Isolated Brownfield setup with Expo SDK 55, including iOS packaging, Android patching, and OTA testing.

cover

Apr 23Talk

State of Brownfield in React Native

A practical look at Brownfield React Native adoption in mature native apps, architecture, tooling, and real-world tradeoffs.

cover

Apr 2Webinar

AI-Assisted Migrations to React Native: From Months to Days

Reduce migration time from months to days by using AI in real React Native workflows. Learn when to migrate incrementally and when starting fresh is the better option.

cover

Nov 17Talk

Vega Developer Meetup

Learn how to integrate React Native into Android TV apps at the Vega Developer Meetup. Workshop, roadmap, and community talks, live in Munich.

cover

May 16Podcast

You Don’t Need a Rewrite, You Need React Native Brownfield

Discover how modern React Native Brownfield integration enables cross-platform power in native apps, without rewriting from scratch.

cover

Jan 13Podcast

Lessons from Shopify's Migration to React Native with Mustafa Ali

Learn how Shopify's migration to React Native led to 86% code unification, reduced build times, and seamless feature parity across platforms.

cover

Jun 14Podcast

Migration to React Native

The 13th episode of React Universe On Air (formerly known as The React Native Show) is fully focused on migrating from different technologies to React Native, the two most popular approaches to this process (greenfield and brownfield software development), and our state-of-the-art brownfield approach.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.