tezvyn:

Building Android Native Modules in React Native

AI-drafted, machine-checkedintermediate

Native modules let JavaScript invoke Android APIs in Kotlin or Java. You build them when no library covers a device capability or you must reuse native code. The classic mistake is blocking the UI thread inside a @ReactMethod or mismatching getName() in JS.

WHY IT EXISTS: React Native runs your application logic inside a JavaScript engine on a separate thread. That sandbox has no direct access to Android system APIs, hardware drivers, or existing native libraries. Native modules were created to punch through that boundary without abandoning the React Native development model, letting teams ship features that depend on platform-specific behavior while keeping the UI layer in JavaScript.

THE MENTAL MODEL: Treat a native module like a tiny RPC server living inside the Android process. You publish a named service from Java or Kotlin. The JavaScript thread sends a message across the bridge, the native side does the work, and the result ferries back through a Promise or Callback. The JS code does not know it is talking to Java; it simply awaits a function call that happens to execute in another language.

HOW IT WORKS: You write a class that extends ReactContextBaseJavaModule and override getName() to define the string JavaScript will use to look it up. Any method you want callable from JS must be public, return void, and carry the @ReactMethod annotation. To send data back, accept a com.facebook.react.bridge.Promise as the final argument and call resolve or reject. Next, you create a ReactPackage implementation that instantiates your module in createNativeModules, and you register that package inside your MainApplication ReactNativeHost getPackages list. On the JS side, you import NativeModules from react-native and invoke NativeModules.YourModuleName.yourMethod.

WHEN TO USE IT: Three places this shows up: first, when you need an Android API such as Bluetooth LE, NFC, or a vendor-specific SDK that has no Expo or community wrapper; second, when your organization already owns complex native business logic in Kotlin or Java and rewriting it in JS would introduce bugs or maintenance debt; third, when you must offload CPU-intensive work such as image resizing or cryptographic hashing off the JavaScript thread to keep the UI responsive.

WHEN NOT TO USE IT: Do not write a native module for business logic that can be expressed safely in JavaScript, because crossing the bridge incurs serialization overhead and adds build complexity. Also avoid it when a mature community library already covers the use case, since maintaining native code means handling Android version upgrades, permission changes, and Gradle conflicts yourself.

ONE CANONICAL EXAMPLE: Imagine you need to read the device battery level. You create a BatteryModule class extending ReactContextBaseJavaModule with getName() returning BatteryModule. Inside, a @ReactMethod getBatteryLevel(Promise promise) grabs the Android BatteryManager, reads the EXTRA_LEVEL intent, and calls promise.resolve with the integer. In your ReactPackage, you add new BatteryModule(reactContext) to the modules list. In JavaScript, you call NativeModules.BatteryModule.getBatteryLevel() and await the number. If you forget to return the exact string BatteryModule from getName() but name the class differently, JS sees undefined and the call fails silently.

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.