Skip to content

Installation

The quick start showed the round trip in isolation. This page is the setup that gets BridgeKit into a real app on each platform: where the native runtime comes from, how it is initialized, and the packaging constraints that matter.

BridgeKit’s transport is built on Nitro modules, so on both platforms the native runtime ships as a prebuilt artifact rather than something you compile from this repo. You add the artifact, initialize the runtime once at process start, then provide/consume contracts as shown in Using BridgeKit.

The Android runtime is the most mature target — full bidirectional round trips (async, fire, streams, state) run on a real device or emulator. Setup is three moving parts: the dependency, the one-time initialization, and provider auto-discovery.

  1. Add the BridgeKit dependency. BridgeKit is a React Native native module backed by react-native-nitro-modules; it ships as an AAR consumed through your app’s existing React Native / Gradle setup. The Nitro headers are delivered as a Prefab AAR (buildFeatures.prefab = true), and a single libBridgeKit.so is built per ABI (armeabi-v7a, x86, x86_64, arm64-v8a).

  2. Initialize the runtime before React Native. Call BridgeKit.default.initialize(host) before ReactManager.initialize(). The initialization seeds the Nitro native delegate (the Router) that every inbound call lands on.

    val bridgeKit = BridgeKit.default
    bridgeKit.initialize(host)
    // … then ReactManager.initialize(...)
  3. Register providers in the provide window. Provider discovery must run before ReactManager.initialize() as well — that is the window in which contracts are provided so the JS side can resolve them on first connect. You can register explicitly:

    bridgeKit.provide(MyHostContract, Scope.Global, eager = true) { MyHostImpl() }

    …or let ServiceLoader-based auto-discovery register feature providers for you. See Discovery & auto-registration for the ServiceLoader contract and the R8 keep rules it needs.

Once initialized, implementing and registering a provider is covered in Kotlin: provide & consume.

iOS is at parity with Android — the same four markers run on-device, validated end to end. The interesting part of iOS setup is packaging: BridgeKit is a Nitro-backed Swift module with C++ interop, and the central constraint is that consumers must not be forced to drag in Nitro/C++ headers. BridgeKit solves this with a synthetic framework-alias approach.

The build pipeline (platforms/ios/Makefile) archives every Swift module and produces a single xcframework:

make all
→ make update-podfile # sets USE_FRAMEWORKS=static in the Podfile
→ xcodebuild archive # iphonesimulator + iphoneos
→ xcodebuild -create-xcframework → Axion.xcframework
→ sanitize-ios-public-module.py # synthetic framework injection

The output is Axion.xcframework with exactly one binary (Axion); all Swift modules — AxionCore, BridgeKit, AxionContracts, and the rest — are statically linked into it. The sanitize step then synthesizes, for each public module (BridgeKit, AxionContracts) and each slice, a thin {Module}.framework whose:

  • Modules/{Module}.swiftmodule/{triple}.swiftinterface is a hand-authored pure-Swift interface — no import NitroModules, no C++ types;
  • binary is a symlink to the real Axion binary.

Consumers see a clean Swift module; the actual code is the single linked Axion binary. This is what lets a consumer import BridgeKit and AxionContracts without enabling C++ interop.

This is the target end-state (it is how Lidl Plus / Connect runs in production):

  • Add Axion.xcframework as a binaryTarget, and put its directory on FRAMEWORK_SEARCH_PATHS (e.g. unsafeFlags: ["-F", "<xcframeworks-search-path>"]).
  • Import the pure-Swift facades — import BridgeKit for the runtime and import AxionContracts for the generated types. The app target links against Axion.
  • No SWIFT_OBJC_INTEROP_MODE, no C++ headers, no CocoaPods.
App.swift
@_implementationOnly import BridgeKit
@_implementationOnly import AxionContracts
let bk = BridgeKitRuntime.default
let hostBinding = bk.provide(AxionHostContract(), scope: .global) { MyHostImpl() }

In app code you do not need to import NitroModules — the C++ seam is hidden inside the framework binary. Providing and consuming from Swift is covered in Swift: provide & consume.