React Native Brownfield

repository·main·Indexed 19 days ago

https://github.com/callstack/react-native-brownfield

Tools 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).

Tokens
76.3K
Snippets
238
Records
336
Agent score
63%

What's inside react-native-brownfield

  1. Overview of the Brownfield CLI

    main

    The 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 useStore hook with selector support to ensure optimal re-renders.
    • SwiftUI Integration: Provides the @UseStore property wrapper for reactive UI updates in SwiftUI.
    • UIKit Support: Offers a subscribe-based API for imperative UI updates in UIKit.
  2. What is React Native Brownfield?

    main

    React 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.
  3. Overview of React Native Brownfield

    main

    React 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.
  4. Understand the React Native Brownfield demo architecture

    main

    The 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 default expo consumer 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 from ExpoApp.
      • vanilla: Consumes the artifact from RNApp.
    • 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)
  5. Install and use @callstack/brownie for shared state

    main

    Brownie provides cross-platform shared state for React Native brownfield applications. The workflow involves defining stores in *.brownie.ts files, running codegen to create native types, and then consuming those typed APIs in TypeScript, Android, and iOS environments.

    Core Workflow

    1. Define: Create store schemas in *.brownie.ts files.
    2. Generate: Run brownfield codegen to generate the necessary native types.
    3. Consume: Use the generated APIs in your React Native code and native host (Android/iOS) code.
    4. Package: Use packaging commands to bundle native artifacts for integration.
    npm install @callstack/brownie
  6. How Brownie synchronization works

    main

    Brownie uses a code generation workflow to ensure type safety across the bridge between TypeScript and native platforms. The workflow follows these steps:

    1. Define: Create your store shape in a *.brownie.ts file using TypeScript.
    2. Generate: Run the brownfield codegen command via the Brownfield CLI to generate native types.
    3. Consume: Use the generated types in your application:
      • React Native: Use the useStore hook.
      • Swift (iOS): Use the @UseStore property wrapper.
      • Kotlin (Android): Use the Store API.
    4. Sync: State changes are automatically synchronized between the TypeScript and native sides.
    ┌─────────────────┐       ┌──────────────────┐       ┌──────────────────────┐
    │   TypeScript    │──────▶│  Brownfield CLI  │──────▶│  Swift/Kotlin types   │
    │   Store Schema  │       │     (codegen)    │       │   (generated)        │
    └─────────────────┘       └──────────────────┘       └──────────────────────┘
  7. Use Android Product Flavors with the Brownfield Plugin

    main

    You 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 :app module.

    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"
        }
    }
  8. Understand the Brownie Data Transfer & Memory Model

    main

    Brownie 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

    1. 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::mutex during all operations.
    • The Swift StoreManager uses NSLock for registry access.
    • Change notifications are dispatched to the main queue using dispatch_async.
  9. Use @UseStore in SwiftUI

    main

    The @UseStore property 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 Equatable for change detection.
    • Bindings: The projected value ($) provides a standard SwiftUI Binding<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 }
          }
        }
      }
    }
  10. Best practices for BrownfieldNavigation

    main

    To ensure a stable integration between React Native and the native host:

    1. Alignment: Keep JavaScript method names strictly aligned with the actual native destinations.
    2. Parameter Stability: Pass stable, explicit parameters (such as userId, IDs, or flags) instead of passing derived UI state.
    3. Initialization: Implement runtime guards during app startup to ensure that native delegate registration always completes before any JavaScript navigation calls are made.