Software Engineering & Digital Products for Global Enterprises since 2006
CMMi Level 3SOC 2ISO 27001
Menu
View all services
Staff Augmentation
Embed senior engineers in your team within weeks.
Dedicated Teams
A ring-fenced squad with PM, leads, and engineers.
Build-Operate-Transfer
We hire, run, and transfer the team to you.
Contract-to-Hire
Try the talent. Convert when you're ready.
ForceHQ
Skill testing, interviews and ranking — powered by AI.
RoboRingo
Build, deploy and monitor voice agents without code.
MailGovern
Policy, retention and compliance for enterprise email.
Vishing
Test and train staff against AI-driven voice attacks.
CyberForceHQ
Continuous, adaptive security training for every team.
IDS Load Balancer
Built for Multi Instance InDesign Server, to distribute jobs.
AutoVAPT.ai
AI agent for continuous, automated vulnerability and penetration testing.
Salesforce + InDesign Connector
Bridge Salesforce data into InDesign to design print catalogues at scale.
View all solutions
Banking, Financial Services & Insurance
Cloud, digital and legacy modernisation across financial entities.
Healthcare
Clinical platforms, patient engagement, and connected medical devices.
Pharma & Life Sciences
Trial systems, regulatory data, and field-force enablement.
Professional Services & Education
Workflow automation, learning platforms, and consulting tooling.
Media & Entertainment
AI video processing, OTT platforms, and content workflows.
Technology & SaaS
Product engineering, integrations, and scale for tech companies.
Retail & eCommerce
Shopify, print catalogues, web-to-print, and order automation.
View all industries
Blog
Engineering notes, opinions, and field reports.
Case Studies
How clients shipped — outcomes, stack, lessons.
White Papers
Deep-dives on AI, talent models, and platforms.
Portfolio
Selected work across industries.
View all resources
About Us
Who we are, our story, and what drives us.
Co-Innovation
How we partner to build new products together.
Careers
Open roles and what it's like to work here.
News
Press, announcements, and industry updates.
Leadership
The people steering MetaDesign.
Locations
Gurugram, Brisbane, Detroit and beyond.
Contact Us
Talk to sales, hiring, or partnerships.
Request TalentStart a Project
Mobile Development

Integrating Native Modules in React Native: Bridging the Gap

GS
Girish Sagar
Technical Content Lead
January 6, 2025
10 min read
Integrating Native Modules in React Native: Bridging the Gap — Mobile Development | MetaDesign Solutions

When JavaScript Isn't Enough: The Case for Native Modules

React Native provides a vast set of cross-platform APIs, but there are scenarios where JavaScript alone cannot solve the problem. Accessing device-specific hardware (biometric sensors, NFC readers, Bluetooth LE peripherals), integrating existing native SDKs (payment processors, analytics libraries, AR frameworks), or performing computationally intensive operations (image processing, video encoding, ML inference) all require native code. Native modules bridge the gap between JavaScript and the platform layer, allowing you to write Swift/Objective-C (iOS) or Kotlin/Java (Android) code and expose it as a JavaScript API that React Native components can call directly.

Old Bridge vs. New Architecture (TurboModules)

React Native's old bridge serialized all communication between JavaScript and native code as JSON messages sent over an asynchronous queue—introducing latency and preventing synchronous calls. The New Architecture (React Native 0.71+) replaces this with TurboModules and the JavaScript Interface (JSI), enabling direct synchronous calls between JavaScript and native code without JSON serialization. TurboModules are lazy-loaded (initialized only when first accessed, reducing startup time) and type-safe (using CodeGen to generate C++ bindings from Flow/TypeScript specs). For new projects, always target the New Architecture with TurboModules.

Creating a Native Module for Android (Kotlin)

Create a Kotlin class implementing `ReactContextBaseJavaModule`: `class DeviceInfoModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext)`. Override `getName()` to return the module name visible to JavaScript. Annotate exposed methods with `@ReactMethod`: `@ReactMethod fun getDeviceName(promise: Promise) { promise.resolve(Build.MODEL) }`. Create a `ReactPackage` implementation that returns your module in `createNativeModules()`. Register the package in `MainApplication.kt`'s `getPackages()` method. In JavaScript, import via `NativeModules.DeviceInfoModule.getDeviceName()`. Use `Promise` for async results and `Callback` for one-shot callbacks.

Creating a Native Module for iOS (Swift)

Create a Swift class: `@objc(DeviceInfoModule) class DeviceInfoModule: NSObject`. Create a bridging header and an Objective-C file with `RCT_EXTERN_MODULE(DeviceInfoModule, NSObject)` and `RCT_EXTERN_METHOD(getDeviceName:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)`. In the Swift class, implement: `@objc func getDeviceName(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { resolve(UIDevice.current.name) }`. Add `@objc static func requiresMainQueueSetup() -> Bool { return false }` if the module doesn't need main thread initialization. The JavaScript API is identical to the Android module.

Sending Events from Native to JavaScript

Native modules often need to push data to JavaScript—sensor readings, download progress, or lifecycle events. On Android, extend `ReactContextBaseJavaModule` and call `reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java).emit("onSensorData", params)`. On iOS, subclass `RCTEventEmitter`, override `supportedEvents()` to return an array of event names, and call `sendEvent(withName: "onSensorData", body: ["value": 42])`. In JavaScript, subscribe using `NativeEventEmitter`: `const emitter = new NativeEventEmitter(NativeModules.SensorModule); const sub = emitter.addListener('onSensorData', (data) => { ... })`. Always call `sub.remove()` on component unmount to prevent memory leaks.

Transform Your Publishing Workflow

Our experts can help you build scalable, API-driven publishing systems tailored to your business.

Book a free consultation

Native UI Components: ViewManagers

Beyond modules (which expose methods), you can expose native views as React components. On Android, create a `SimpleViewManager` subclass that wraps a native view (e.g., a custom camera preview). Override `createViewInstance()` to return the native view and use `@ReactProp` annotations to expose configurable properties. On iOS, subclass `RCTViewManager`, implement `view()` to return the native UIView, and declare properties with `RCT_EXPORT_VIEW_PROPERTY`. In JavaScript, use `requireNativeComponent('RCTCameraPreview')` to create a React component that renders the native view. This is how libraries like `react-native-maps` and `react-native-video` work.

Building TurboModules with the New Architecture

TurboModules require a spec file (TypeScript or Flow) that defines the module's interface: `export interface Spec extends TurboModule { getDeviceName(): Promise; }`. Run CodeGen to generate C++ bindings from the spec. Implement the module in Kotlin/Swift as before, but extend the generated abstract class instead of `ReactContextBaseJavaModule`. The generated C++ layer handles JSI bindings—enabling direct JavaScript-to-native calls without bridge serialization. TurboModules are lazy-loaded by default, meaning they're initialized only when first accessed by JavaScript, improving app startup time for modules not immediately needed.

Testing, Debugging, and Cross-Platform Consistency

Maintain API parity between Android and iOS modules—same method names, same parameter types, same return types. Create a TypeScript wrapper that imports `NativeModules` and provides typed, platform-agnostic methods with runtime checks: `const DeviceInfo = Platform.OS === 'ios' ? NativeModules.DeviceInfoModule : NativeModules.DeviceInfoModule`. Write unit tests for native code using XCTest (iOS) and JUnit (Android). For integration testing, use Detox or Appium to verify the JavaScript-to-native round trip. Debug native code using Xcode's debugger (iOS) and Android Studio's debugger (Android) while the React Native app is running—set breakpoints in native methods to trace execution.

FAQ

Frequently Asked Questions

Common questions about this topic, answered by our engineering team.

Use native modules when you need device-specific hardware access (biometrics, NFC, Bluetooth), integration with existing native SDKs (payment processors, AR frameworks), computationally intensive operations (image processing, ML inference), or access to platform APIs not exposed by React Native.

The old bridge serialized all communication as JSON over an async queue. TurboModules use JSI for direct synchronous JavaScript-to-native calls without serialization. TurboModules are also lazy-loaded (initialized on first access) and type-safe via CodeGen-generated C++ bindings.

On Android, use RCTDeviceEventEmitter to emit events. On iOS, subclass RCTEventEmitter and call sendEvent. In JavaScript, subscribe with NativeEventEmitter's addListener. Always remove listeners on component unmount to prevent memory leaks.

Create a ViewManager (Android) or RCTViewManager (iOS) that wraps a native view. Expose properties via @ReactProp (Android) or RCT_EXPORT_VIEW_PROPERTY (iOS). In JavaScript, use requireNativeComponent() to create a React component that renders the native view.

Use identical method names, parameter types, and return types on both platforms. Create a TypeScript wrapper that provides typed, platform-agnostic methods. Write unit tests per platform (XCTest, JUnit) and integration tests (Detox) to verify the JavaScript-to-native round trip.

Discussion

Join the Conversation

Ready when you are

Let's build something great together.

A 30-minute call with a principal engineer. We'll listen, sketch, and tell you whether we're the right partner — even if the answer is no.

Talk to a strategist
Need help with your project? Let's talk.
Book a call