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.
Android
Section titled “Android”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.
-
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 singlelibBridgeKit.sois built per ABI (armeabi-v7a,x86,x86_64,arm64-v8a). -
Initialize the runtime before React Native. Call
BridgeKit.default.initialize(host)beforeReactManager.initialize(). The initialization seeds the Nitro native delegate (theRouter) that every inbound call lands on.val bridgeKit = BridgeKit.defaultbridgeKit.initialize(host)// … then ReactManager.initialize(...) -
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 theServiceLoadercontract 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.
What ships
Section titled “What ships”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 injectionThe 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}.swiftinterfaceis a hand-authored pure-Swift interface — noimport NitroModules, no C++ types;- binary is a symlink to the real
Axionbinary.
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.
Consuming it
Section titled “Consuming it”This is the target end-state (it is how Lidl Plus / Connect runs in production):
- Add
Axion.xcframeworkas abinaryTarget, and put its directory onFRAMEWORK_SEARCH_PATHS(e.g.unsafeFlags: ["-F", "<xcframeworks-search-path>"]). - Import the pure-Swift facades —
import BridgeKitfor the runtime andimport AxionContractsfor the generated types. The app target links againstAxion. - No
SWIFT_OBJC_INTEROP_MODE, no C++ headers, no CocoaPods.
@_implementationOnly import BridgeKit@_implementationOnly import AxionContracts
let bk = BridgeKitRuntime.defaultlet hostBinding = bk.provide(AxionHostContract(), scope: .global) { MyHostImpl() }The generated Swift bindings are delivered through a separate AxionContracts pod
(kept distinct from AxionCore.podspec — two podspecs at the @axion/core root would break
NitroActions autolinking):
pod 'AxionContracts', :path => '../../packages/core/ios/contracts'The AxionContracts pod sets objcxx + c++20 and depends on BridgeKit. The Podfile
enforces static linking via ENV['USE_FRAMEWORKS'] = "static" (applied by
make update-podfile before archiving).
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.
Where to next
Section titled “Where to next”- Kotlin: provide & consume — implement and register a provider on Android.
- Swift: provide & consume — the same on iOS.
- Quick start — the end-to-end round trip if you skipped it.