React Native Native Modules

React Native covers most features through JavaScript libraries. Some device abilities have no ready-made library: a vendor payment terminal, a custom Bluetooth gadget, or a specialized scanner. A native module fills that gap. It lets JavaScript call code written in Kotlin or Java for Android and Swift or Objective-C for iOS.

The Bridge Between Two Worlds

┌─────────────────────┐          ┌──────────────────────┐
│  JavaScript Layer   │          │     Native Layer     │
│  (your screens)     │          │ (Kotlin / Swift)     │
│                     │  call    │                      │
│ Battery.getLevel() ─┼────────► │ reads system battery │
│                     │ ◄────────┼─ returns 82          │
└─────────────────────┘  result  └──────────────────────┘

JavaScript asks, native code works, and the answer travels back. Think of a translator between two people who speak different languages.

When a Native Module Makes Sense

  • A needed platform feature has no existing library
  • An existing native SDK from a vendor must run inside the app
  • A heavy calculation runs faster in native code
  • A company shares one native codebase across several apps

Search the library directory first. A maintained package often exists, and it saves weeks of work.

Project Requirement

Native modules need access to the android and ios folders. A basic Expo Go session cannot load custom native code. Use a development build, or generate the native folders with a prebuild command.

npx expo prebuild

Example: A Device Name Module on Android

The Kotlin class below exposes one method that returns the device model.

// android/app/src/main/java/com/myapp/DeviceInfoModule.kt
package com.myapp

import android.os.Build
import com.facebook.react.bridge.*

class DeviceInfoModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {

    override fun getName() = "DeviceInfo"

    @ReactMethod
    fun getModel(promise: Promise) {
        try {
            promise.resolve(Build.MODEL)
        } catch (e: Exception) {
            promise.reject("ERR_MODEL", e)
        }
    }
}

Register the Module

A package class lists the module so React Native can discover it.

class DeviceInfoPackage : ReactPackage {
    override fun createNativeModules(ctx: ReactApplicationContext) =
        listOf(DeviceInfoModule(ctx))

    override fun createViewManagers(ctx: ReactApplicationContext) =
        emptyList<ViewManager<*, *>>()
}

Add DeviceInfoPackage() to the package list inside MainApplication.

Call It from JavaScript

import { NativeModules } from 'react-native';

const { DeviceInfo } = NativeModules;

const showModel = async () => {
  try {
    const model = await DeviceInfo.getModel();
    console.log('Model:', model);
  } catch (e) {
    console.log('Could not read model');
  }
};

Promises carry results back to JavaScript. A method can also receive a callback or emit events for ongoing updates.

Three Ways Native Code Talks Back

Method          Best For
──────────────────────────────────────────
Promise         A single result (async/await)
Callback        Older style single result
Event emitter   Repeated updates (sensor streams)

Native UI Components

A native module supplies logic. A native UI component supplies a visible element, such as a custom map or a chart drawn by platform code. Native UI components use a view manager class, and JavaScript displays the result like any other tag.

<CustomChartView style={{ width: 300, height: 200 }} data={values} />

The New Architecture

Recent React Native versions introduce TurboModules and Fabric. TurboModules load native code on demand and call it directly, with less overhead than the older bridge. Typed specifications describe each method, and a code generator creates the glue code. Existing modules continue to work through a compatibility layer, so projects migrate gradually.

Old Bridge                   New Architecture
──────────────────           ─────────────────────────
Messages queue up            Direct calls
Data converted to JSON       Shared memory access
All modules load at start    Modules load when needed

Testing and Maintenance

  • Test on real Android and iOS devices
  • Handle errors in native code and return clear messages
  • Keep method names identical on both platforms
  • Document the module for the whole team
  • Rebuild the app after every native change, since a JavaScript reload does not update native code

Summary

Native modules connect JavaScript to platform code when no library covers a need. Write the Kotlin or Swift class, register it, and call it through NativeModules using promises. Search for existing packages first, use development builds, and learn the New Architecture for faster, typed communication.