Building a Native Module for React Native (Android)

Developing a Native Module for React Native (Android) Imagine you need to connect a specific Bluetooth device to your React Native app, but the standard libraries don't provide the needed control. JavaScript cannot directly call Android APIs—you need a Native Module. An error in its implementatio

Development and support of all types of mobile applications:

Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1All 1734 services
Building a Native Module for React Native (Android)
Complex
~3-5 days

Our competencies:

Frequently Asked Questions

Latest works

  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    894
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    597

Developing a Native Module for React Native (Android)

Imagine you need to connect a specific Bluetooth device to your React Native app, but the standard libraries don't provide the needed control. JavaScript cannot directly call Android APIs—you need a Native Module. An error in its implementation can crash the app or cause memory leaks. Over 5 years, we have completed more than 30 projects with Native Modules and know the typical pitfalls. Let's break it down using Bluetooth LE as an example.

A Native Module is a bridge between JS and native Android code. React Native offers two approaches: the classic Bridge and the new TurboModule based on JSI. The architecture choice directly affects performance: TurboModule provides 1–2 ms latency versus 10–20 ms for Bridge—a gain of up to 10x for high-frequency calls. This difference is noticeable with continuous data streams (audio, sensors).

Implementing a Bridge Module

For projects using the old architecture (without JSI support), use the classic approach.

// BluetoothModule.kt class BluetoothModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { override fun getName(): String = "BluetoothModule" @ReactMethod fun isBluetoothEnabled(promise: Promise) { val bluetoothManager = reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager promise.resolve(bluetoothManager.adapter?.isEnabled ?: false) } @ReactMethod fun startScan(promise: Promise) { val scanner = (reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager) .adapter?.bluetoothLeScanner if (scanner == null) { promise.reject("BT_ERROR", "Bluetooth LE not supported") return } promise.resolve(null) } private fun sendEvent(eventName: String, params: WritableMap?) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(eventName, params) } } 

Register the module via a ReactPackage and add it to MainApplication.kt in the getPackages() method.

How to Choose Between Bridge and TurboModule?

Criterion Bridge (old architecture) TurboModule (new architecture)
Performance Call via JSON serialization, latency ~10-20 ms JSI without serialization, latency ~1-2 ms
Compatibility Projects without JSI support Projects with new architecture (JSI)
Support Deprecated but still widely used Future of React Native, active development
Complexity Simpler, no Codegen required Requires TypeScript spec and code generation

For high-frequency calls (audio, sensors), TurboModule provides a performance gain of up to 10x. The development cost of such a module pays off by reducing revision time by 30%.

Why Is TurboModule Faster Than Bridge?

TurboModule uses JSI (JavaScript Interface) to call native functions directly, bypassing JSON serialization. Bridge packages each call into JSON, adding overhead. For single calls the difference is negligible, but at 1000 calls per second, TurboModule saves up to 20 ms—critical for animations or sensor processing.

How to Pass Events from Native Code to JS?

The pattern is the same for both architectures—use RCTDeviceEventEmitter.

fun emitScanResult(deviceAddress: String, rssi: Int) { val params = Arguments.createMap().apply { putString("address", deviceAddress) putInt("rssi", rssi) } reactApplicationContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("onBluetoothDeviceFound", params) } 

On the JS side, subscribe via NativeEventEmitter:

import { NativeEventEmitter, NativeModules } from 'react-native'; const { BluetoothModule } = NativeModules; const emitter = new NativeEventEmitter(BluetoothModule); useEffect(() => { const subscription = emitter.addListener('onBluetoothDeviceFound', (event) => { console.log('Found device:', event.address, 'RSSI:', event.rssi); }); return () => subscription.remove(); }, []); 

Always call subscription.remove() in the cleanup—a subscription leak will cause the handler to fire after the component unmounts.

Permission Requests: JS Layer Responsibility

The native module should not request runtime permissions itself—that is the JS layer's responsibility using react-native-permissions or PermissionsAndroid. The module only checks availability and returns an error. For example, if BLUETOOTH_SCAN permission is not granted, the module returns a PERMISSION_DENIED error.

Using Codegen for TurboModule

When using TurboModule, you need to create a TypeScript specification and run Codegen. This automatically generates Kotlin interfaces and JNI code. Example:

export interface Spec extends TurboModule { readonly getConstants: () => { [key: string]: any }; isBluetoothEnabled(): Promise<boolean>; startScan(): Promise<void>; } 

Codegen reduces implementation time by 30% and eliminates manual binding errors.

Testing the Module

We test the native module at the Kotlin level (JUnit + Mockk) and at the integration level via Detox. For example, testing that isBluetoothEnabled returns false when Bluetooth is absent:

@Test fun `isBluetoothEnabled returns false when adapter is null`() { val context = mockk<ReactApplicationContext>() every { context.getSystemService(Context.BLUETOOTH_SERVICE) } returns mockk<BluetoothManager> { every { adapter } returns null } val module = BluetoothModule(context) val promise = mockk<Promise>(relaxed = true) module.isBluetoothEnabled(promise) verify { promise.resolve(false) } } 

It is also important to test error handling and lifecycle. Thanks to unit tests, clients save up to 30% of the debugging budget.

Typical Development Mistakes

  • Context leak: Do not store a reference to ReactApplicationContext in a static field—use WeakReference.
  • Ignoring ProGuard: Add -keep rules for your module, otherwise the production build will crash with ClassNotFoundException.
  • Mixing architectures: Do not use Bridge methods in a TurboModule project—the signature will break.

What Is Included in Native Module Development

Component Description
API specification TypeScript interface for Codegen (if TurboModule) or method declarations for Bridge
Native implementation Module class in Kotlin handling all cases
Unit tests Testing the module in Kotlin with mocked dependencies
Integration into project Setting up Package, permissions, and Gradle
Documentation API, examples, error handling
Post-delivery support 30 days for bug fixes

Work Process

  1. Requirements analysis — study Android API, agree on method signatures.
  2. Specification design — create TypeScript interface or JS proxy.
  3. Native code implementation — write module in Kotlin, handle edge cases.
  4. Testing — unit tests + integration in a sample project.
  5. Integration and documentation — connect to your app, update docs.
  6. Delivery — source code, demo project, build instructions.

Timelines: Basic module with 3–5 methods — 2–4 days; module with events and tests — from one week. Cost is calculated individually. Request a consultation to get an accurate estimate for your project and a free requirements analysis. Contact us within one business day.