React Native Brownfield
repository·main·Indexed 19 days ago
https://github.com/callstack/react-native-brownfieldTools and helpers to integrate React Native into existing native iOS and Android applications, enabling incremental adoption and coexistence between native and React Native code. Includes a Gradle plugin for Android to manage fat Aar dependencies and demo projects illustrating integration with vanilla React Native and Expo (SDK v56 and v57).
What's inside react-native-brownfield
- Brownie is a shared state management library designed for React Native brownfield applications. It provides a single source of truth that enables seamless state synchronization between TypeScript (React Native) and native code (Swift/iOS and Kotlin/Android).
Overview of the Brownfield CLI
mainThe Brownfield CLI is a tool designed for use with Brownie and Brownfield. Its primary purpose is to facilitate the generation of state management code and to assist in packaging and publishing Brownfield artifacts.
Key capabilities include:
- Shared State: Enables a single source of truth that is accessible from both TypeScript and Swift.
- Type Safety: Provides full type inference, automatically generating Swift types from TypeScript schemas.
- React Integration: Supports the
useStorehook with selector support to ensure optimal re-renders. - SwiftUI Integration: Provides the
@UseStoreproperty wrapper for reactive UI updates in SwiftUI. - UIKit Support: Offers a subscribe-based API for imperative UI updates in UIKit.
What is React Native Brownfield?
mainReact Native Brownfield is a library designed to simplify the integration of React Native into existing native iOS and Android applications. It allows developers to embed React Native views and view controllers directly into a native app's navigation stack.
Key capabilities include:
- Starting React Native with a single method call and invoking code immediately upon load.
- Reusing a single React Native instance across multiple components.
- Compatibility with both legacy and new React Native architectures.
- Support for modern native UI frameworks: UIKit and SwiftUI (iOS), and Fragments and Jetpack Compose (Android).
- Ability to control native gestures and hardware buttons from JavaScript.
- Seamless integration with Expo via an Expo Config Plugin.
- Flexibility to work with any native navigation pattern or JavaScript-based navigation.
Overview of React Native Brownfield
mainReact Native Brownfield is a set of helpers designed to facilitate the smooth integration of React Native into existing native applications (brownfield integration). It allows developers to incrementally adopt React Native without rewriting the entire app.
Key capabilities include:
- Single-method startup: Start React Native and invoke code immediately upon loading.
- Architecture Compatibility: Supports both legacy and the new React Native architecture.
- Instance Reuse: Reuse the same React Native instance across different native components.
- Native UI Support: Compatible with UIKit/SwiftUI (iOS) and Fragments/Jetpack Compose (Android).
- Language Support: Works with Objective-C, Swift, Java, and Kotlin.
- Navigation Flexibility: Works with any native navigation pattern or JavaScript-based navigation.
- Gesture Control: Ability to enable/disable native gestures and hardware buttons from JavaScript.
- Automated Build: Includes a CLI to automate the build process.
Understand the React Native Brownfield demo architecture
mainThe
apps/directory contains several demo projects that illustrate how to integrate React Native into existing native applications using different configurations:React Native Providers (The libraries being integrated)
RNApp: A standard React Native application packaged into AAR (Android) and XCFramework (iOS) archives for integration into native projects.ExpoApp56: An Expo application using Expo SDK v56 (demonstrates Expo Updates).ExpoApp57: An Expo application using Expo SDK v57 (uses the defaultexpoconsumer alias).ExpoAppPreview: A temporary Expo app used for testing new Expo preview releases.
Native Consumers (The host applications)
AndroidApp: A native Android application that consumes the RN packages. It has two flavors:expo: Consumes the artifact fromExpoApp.vanilla: Consumes the artifact fromRNApp.
AppleApp: A native iOS application that consumes packaged XCFrameworks. It defines specific targets for each provider:Brownfield Apple App (RNApp)(Vanilla)Brownfield Apple App (ExpoApp56)Brownfield Apple App (ExpoApp57)Brownfield Apple App (ExpoAppPreview)
Install and use @callstack/brownie for shared state
mainBrownie provides cross-platform shared state for React Native brownfield applications. The workflow involves defining stores in
*.brownie.tsfiles, running codegen to create native types, and then consuming those typed APIs in TypeScript, Android, and iOS environments.Core Workflow
- Define: Create store schemas in
*.brownie.tsfiles. - Generate: Run
brownfield codegento generate the necessary native types. - Consume: Use the generated APIs in your React Native code and native host (Android/iOS) code.
- Package: Use packaging commands to bundle native artifacts for integration.
npm install @callstack/brownie- Define: Create store schemas in
How Brownie synchronization works
mainBrownie uses a code generation workflow to ensure type safety across the bridge between TypeScript and native platforms. The workflow follows these steps:
- Define: Create your store shape in a
*.brownie.tsfile using TypeScript. - Generate: Run the
brownfield codegencommand via the Brownfield CLI to generate native types. - Consume: Use the generated types in your application:
- React Native: Use the
useStorehook. - Swift (iOS): Use the
@UseStoreproperty wrapper. - Kotlin (Android): Use the
StoreAPI.
- React Native: Use the
- Sync: State changes are automatically synchronized between the TypeScript and native sides.
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │ TypeScript │──────▶│ Brownfield CLI │──────▶│ Swift/Kotlin types │ │ Store Schema │ │ (codegen) │ │ (generated) │ └─────────────────┘ └──────────────────┘ └──────────────────────┘- Define: Create your store shape in a
Use Android Product Flavors with the Brownfield Plugin
mainYou can use product flavors and dimensions in your brownfield module as usual. However, there is a strict requirement: any flavor and dimension defined in your brownfield module must also be defined in the consuming
:appmodule.If the flavors match, the plugin can correctly look up tasks for JavaScript bundling, Expo resources, and native binaries. If there is a mismatch, the build will fail.
// In your brownfield module flavorDimensions += "env" productFlavors { create("prod") { dimension = "env" } create("dev") { dimension = "env" } }Configure Expo SDK compile-time defines for iOS
mainWhen using Expo with React Native Brownfield on iOS, the
ReactBrownfieldiOS Pods project (specificallyExpoHostRuntime.swift) requires theEXPO_SDK_GTE_55compile-time define.This define is automatically added to the project via the Podfile, which is injected by the Expo config plugin for Expo SDK versions greater than or equal to 55.
Understand the Brownie Data Transfer & Memory Model
mainBrownie uses a multi-layered data flow model where data is passed between layers via full copies. There is no shared memory or zero-copy optimization currently implemented; each layer (JS, C++, and Swift/ObjC) maintains its own independent representation of the state.
Data Flow Path
- JS Object (jsi) $\leftrightarrow$ (copy) $\leftrightarrow$ folly::dynamic (C++) $\leftrightarrow$ (copy) $\leftrightarrow$ NSDictionary / Swift (ObjC/Swift)
Performance Considerations
Because of the copying mechanism, the number of copies per operation varies:
- JS read/write single property: 2 copies.
- Swift read/write full state: 4 copies (involving JSON serialization/deserialization cycles).
Thread Safety
- The C++ store is protected by
std::mutexduring all operations. - The Swift
StoreManagerusesNSLockfor registry access. - Change notifications are dispatched to the main queue using
dispatch_async.
Use @UseStore in SwiftUI
mainThe
@UseStoreproperty wrapper provides reactive, type-safe access to a specific slice of the Brownfield state using a KeyPath selector.Key features:
- Re-renders: The view only re-renders when the specifically selected value changes.
- Type Safety: Access is governed by the type of the selected KeyPath.
- Requirement: Selected values must conform to
Equatablefor change detection. - Bindings: The projected value (
$) provides a standard SwiftUIBinding<Value>for two-way data binding.
Updating State: Use the
.set { ... }extension on the projected value to update state using a closure based on the current value.import Brownie import SwiftUI struct CounterView: View { @UseStore(\BrownfieldStore.counter) var counter var body: some View { VStack { Text("Count: \(Int(counter))") Button("Increment") { $counter.set { $0 + 1 } } } } }Best practices for BrownfieldNavigation
mainTo ensure a stable integration between React Native and the native host:
- Alignment: Keep JavaScript method names strictly aligned with the actual native destinations.
- Parameter Stability: Pass stable, explicit parameters (such as
userId, IDs, or flags) instead of passing derived UI state. - Initialization: Implement runtime guards during app startup to ensure that native delegate registration always completes before any JavaScript navigation calls are made.