Skip to content
tezvyn:
Menu

Building Android Native Modules in React Native

MediumHow cards are made

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.

Interview question

What happens when the string returned by getName() in a Kotlin native module does not match the name used in JavaScript?

  • a.JavaScript cannot locate the module because the bridge lookup name differs from the registered nameCorrect
  • b.The @ReactMethod annotation is ignored and the method remains inaccessible to the bridge
  • c.The Android build fails at compile time because the package registry detects the mismatch
  • d.The method executes on the native side but the Promise never resolves back to JavaScript
Why?

The card states that getName() defines the exact string JavaScript uses to look up the module, so a mismatch prevents the bridge from finding it. Distractor A is tempting because Promises are used for results, but the module is never reached in the first place.

Just read this? Test yourself on what you have been reading.

Put your scrolling time to good use

Learn one idea, try a quiz and save useful cards for revision. Tezvyn makes it easy to learn and stay current in your tech field, a few minutes at a time.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon

We are hiring for this. Open roles that interview on react native — each one lists the topics its interview covers.

See open roles