skip to content

A bank wants to add React Native screens to its existing native iOS and Android apps; how do you embed React Native, and what are the main pitfalls?

level: seniorimportance: should knowfreq 35%

answer

  1. native projects move under ios/ and android/
  2. AppRegistry name is the contract
  3. RCTReactNativeFactory on iOS
  4. ReactHost and ReactActivity on Android
  5. Metro in debug, embedded bundle in release

basics

~20 s

Add React Native to the native apps, register a root component with AppRegistry, and host it natively: RCTReactNativeFactory on iOS, a ReactHost with a ReactActivity or ReactFragment on Android. Watch names, release bundling and minimum OS versions.

solid answer

~50 s

The documented integrated approach puts a JavaScript root (a `package.json` taken from the Community Template) above the existing apps, moved into `ios/` and `android/`. iOS adds React Native through the template's `Podfile` and `pod install`; Android applies the `com.facebook.react.settings` plugin with `autolinkLibrariesFromCommand()` in `settings.gradle`, and the `com.facebook.react` plugin plus `autolinkLibrariesWithApp()` in the app module. JavaScript registers a screen with `AppRegistry.registerComponent('Payments', () => App)`. iOS shows it through `RCTReactNativeFactory` and its `rootViewFactory`; Android makes the `Application` a `ReactApplication` exposing a `ReactHost`, then hosts the screen in a `ReactActivity` or `ReactFragment`. Pitfalls: the registered name must match on both sides, release builds must embed the JS bundle instead of loading it from Metro (on iOS that needs a bundling build phase added by hand), the host apps must meet React Native's minimums (iOS 15.1, Android minSdk 24), and the native team may prefer a prebuilt isolated package over a Node toolchain.

code

swift · 31 lines
swift
import UIKit
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider

class PaymentsViewController: UIViewController {
  var reactNativeFactory: RCTReactNativeFactory?
  var reactNativeFactoryDelegate: RCTReactNativeFactoryDelegate?

  override func viewDidLoad() {
    super.viewDidLoad()
    reactNativeFactoryDelegate = ReactNativeDelegate()
    reactNativeFactoryDelegate!.dependencyProvider = RCTAppDependencyProvider()
    reactNativeFactory = RCTReactNativeFactory(delegate: reactNativeFactoryDelegate!)
    view = reactNativeFactory!.rootViewFactory.view(withModuleName: "Payments")
  }
}

class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
  override func sourceURL(for bridge: RCTBridge) -> URL? {
    self.bundleURL()
  }

  override func bundleURL() -> URL? {
    #if DEBUG
    RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
    #else
    Bundle.main.url(forResource: "main", withExtension: "jsbundle")
    #endif
  }
}

go deeper

for a junior

Recall that React Native can be added to an existing native app for a single screen, registered in JavaScript with AppRegistry.

for a middle

Explain the integration steps on both platforms: dependencies, Gradle and CocoaPods setup, the JavaScript entry, and the native host classes.

for a senior

Anticipate the pitfalls: name mismatches, release bundling, minimum OS versions, navigation and auth seams, and native build ownership.

for a principal

Choose integrated or isolated delivery for the organisation, and define how native and React Native teams share releases and responsibility.

## What brownfield means A **brownfield** app is an existing native app whose entry point is not React Native; React Native is added for one screen, one flow or one feature. A bank with mature Swift and Kotlin apps adding React Native payment screens is the classic case. The React Native docs cover it in *Integration with Existing Apps*, separately from starting a new project. ## The integrated approach, step by step 1. **Directory structure.** Create a new root folder, put a `package.json` from the Community Template in it, and move the existing projects into `ios/` and `android/`. 2. **JavaScript dependencies.** Install `react-native` and the template's dependencies; `node_modules` is ignored by git. 3. **iOS.** Use the template's `Podfile` as the reference (adjusting its target name), then `bundle exec pod install`. 4. **Android.** In `settings.gradle`, include the React Native Gradle plugin, apply `com.facebook.react.settings` and call `autolinkLibrariesFromCommand()`. In the app's `build.gradle`, apply `com.facebook.react`, depend on `react-android` and `hermes-android`, and call `autolinkLibrariesWithApp()` in the `react` block. 5. **JavaScript entry.** An `index.js` registers each screen: `AppRegistry.registerComponent('Payments', () => App)`. 6. **Native hosts.** Show the registered component from native code (below). ## Hosting the screen | | iOS | Android | |---|---|---| | Runtime setup | an `RCTReactNativeFactory` created with a delegate subclassing `RCTDefaultReactNativeFactoryDelegate` | the `Application` implements `ReactApplication`, exposes `reactHost` from `getDefaultReactHost(...)` and calls `SoLoader.init` in `onCreate` | | Showing a screen | `factory.rootViewFactory.view(withModuleName: "Payments")` in a view controller | a `ReactActivity` whose `getMainComponentName()` returns `"Payments"`, or a `ReactFragment` built with `ReactFragment.Builder()` | | Initial data | the `initialProperties` overload of the view factory | launch options, for example `ReactFragment.Builder().setLaunchOptions(...)` | | Bundle location | `RCTBundleURLProvider` (Metro) in `DEBUG`, `main.jsbundle` in release | Metro in debug, the bundled asset in release | ## Pitfalls - **Name mismatch.** The string in `registerComponent` and the one the native host requests must be identical, or the screen fails with `"Payments" has not been registered`. - **Release bundles.** Debug builds fetch JavaScript from Metro; release builds must embed the bundle. On Android the React Native Gradle plugin bundles it into the APK or App Bundle; on iOS a brownfield app must add a *Bundle React Native code and images* run-script build phase by hand. Test a release build before handing it to QA. - **Minimum versions.** React Native 0.87 requires iOS 15.1 and Android minSdk 24, and uses the New Architecture only; an older host app must raise its deployment targets first. - **Theme on Android.** The React Native activity needs a theme without an ActionBar, or an ActionBar is drawn above the screen. - **Navigation seams.** Back navigation, deep links and authentication state cross the native and JavaScript boundary; decide which side owns each before building. - **Build ownership.** Native engineers now need Node.js, CocoaPods and the React Native Gradle plugin in their workflow. ## The isolated alternative Expo's brownfield guide describes a second approach: build the React Native part separately and ship it to the native apps as a prebuilt library, an **AAR** for Android and an **XCFramework** for iOS. Native teams then consume it like any other dependency and never touch Node.js. It fits organisations with separate native and React Native teams, at the cost of a packaging pipeline and slower iteration across the boundary. ## Choosing between them Choose **integrated** when one team works on both sides and iterates on them together. Choose **isolated** when native teams must stay free of the JavaScript toolchain, or release cadences differ. Either way, start with one low-risk screen, measure startup and memory on real devices, and only then widen the scope. For a bank, that first screen is better a read-only one, such as a statement view, than a payment flow, so the integration is proven before it carries money.

  • How do you pass the signed-in customer's ID to the React Native screen?
    Pass it as initial properties when creating the root view: on iOS through the `initialProperties` overload of `rootViewFactory.view(withModuleName:)`, on Android through launch options such as `ReactFragment.Builder().setLaunchOptions(...)`. The root component receives them as props, so the first render needs no extra native call.
  • When would the isolated approach beat the integrated one for the bank?
    When the native teams must not adopt Node.js and CocoaPods or Gradle plugin changes, or ship on a different cadence. Packaging the React Native part as an AAR and an XCFramework lets them consume it like any SDK, at the cost of a packaging pipeline and slower cross-boundary iteration.

saying these in an interview costs you the question

  • Brownfield apps must be rewritten as React Native apps first
  • The registered component name can differ from the name native requests
  • Release builds still load JavaScript from the Metro dev server
  • A brownfield host can keep the legacy architecture enabled on 0.87
  • Any iOS deployment target works with React Native 0.87