Native Module Development for React Native App (Android)

TRUETECH is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
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 1 servicesAll 1735 services
Native Module Development for React Native App (Android)
Complex
~3-5 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Developing a Native Module for React Native Applications (Android)

A Native Module is needed when JavaScript cannot reach the required Android API: Bluetooth LE, NFC, vendor-specific hardware SDK, or file system operations below what react-native-fs provides. In 2024, React Native offers two paths: the old architecture (Bridge) and the new one (JSI + TurboModules). Projects created with react-native init version 0.74+ use the new architecture by default.

Old Architecture: Bridge Module

For projects on RN < 0.68 or with the new architecture disabled:

// 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
        }
        // start scanning
        promise.resolve(null)
    }

    // Send events to JS
    private fun sendEvent(eventName: String, params: WritableMap?) {
        reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
            .emit(eventName, params)
    }
}

Register via Package:

class BluetoothPackage : ReactPackage {
    override fun createNativeModules(context: ReactApplicationContext) =
        listOf(BluetoothModule(context))
    override fun createViewManagers(context: ReactApplicationContext) = emptyList<ViewManager<*, *>>()
}

Add to MainApplication.kt in the getPackages() method.

New Architecture: TurboModule + JSI

With RN 0.74+ and newArchEnabled=true, implement the module as a TurboModule via Codegen. First, a TypeScript specification:

// NativeBluetoothModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  isBluetoothEnabled(): Promise<boolean>;
  startScan(): Promise<void>;
  addListener(eventType: string): void;
  removeListeners(count: number): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('BluetoothModule');

Codegen generates NativeBluetoothModuleSpec.kt from this specification. The native implementation inherits the generated abstract class:

class BluetoothModule(context: ReactApplicationContext) :
    NativeBluetoothModuleSpec(context) {

    override fun getName() = NAME

    override fun isBluetoothEnabled(): Promise<Boolean> {
        // implementation identical to bridge variant
    }

    companion object {
        const val NAME = "BluetoothModule"
    }
}

The key difference: TurboModule is called directly via JSI, without JSON bridge serialization. For high-frequency calls (audio stream, sensors), this is critical—latency drops from tens of milliseconds to single digits.

Sending Events from Native Code to JavaScript

The pattern is the same for both architectures—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:

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();
}, []);

subscription.remove() in the cleanup of useEffect is mandatory. Subscription leaks cause handler invocation after component unmount and potential state updates on unmounted components.

Handling Permissions Inside the Module

The native module should not request runtime permissions itself—that's the responsibility of the JS layer via react-native-permissions or the built-in PermissionsAndroid. The module only checks if permission is granted and returns an appropriate error:

@ReactMethod
fun startScan(promise: Promise) {
    if (ActivityCompat.checkSelfPermission(
            reactContext,
            Manifest.permission.BLUETOOTH_SCAN
        ) != PackageManager.PERMISSION_GRANTED) {
        promise.reject("PERMISSION_DENIED", "BLUETOOTH_SCAN permission required")
        return
    }
    // continuation
}

Debugging and Common Errors

Module not found: null is not an object (evaluating 'NativeModules.BluetoothModule.isBluetoothEnabled')—Package not added to getPackages() or typo in getName(). Check Metro bundler logs and adb logcat.

Calling native method from background thread updates UI. React Native bridge and JSI are not main thread. If updating any Android UI element inside @ReactMethod, use Handler(Looper.getMainLooper()).post { ... }.

WritableMap cannot be used after passing to promise. Arguments.createMap() creates a one-time object. After promise.resolve(map), the object is invalid—re-reading it causes IllegalStateException.

Compatibility between old and new architectures. Until the project fully transitions to the new architecture, the module must support both. ReactPackage remains, TurboModule implementation is added on top. In build.gradle, use conditional compilation with isNewArchEnabled.

Testing

Native modules are tested at the Kotlin level (JUnit + Mockk for ReactApplicationContext) and at integration level via Detox or Maestro. Isolated bridge method test:

@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) }
}

Developing a Native Module: basic module with 3–5 methods takes 2–4 days. Module with events, support for both architectures, and tests takes a week or more. Integrating heavy vendor SDK is calculated individually. Cost is determined after analyzing requirements.