Writing Native Modules (Android & iOS)
📖 Concept
Native modules let you write platform-specific code (Kotlin/Java for Android, Swift/ObjC for iOS) and call it from JavaScript. This is necessary when:
- No existing JS/RN library covers your need
- You need to access platform APIs not exposed by React Native
- Performance-critical code that must run natively (image processing, encryption)
- Integrating third-party native SDKs
Old architecture (Bridge-based) native module:
- Register module with the bridge
- Methods are async-only (return via Promise or callback)
- Data crosses the bridge as JSON — serialization overhead
New architecture (TurboModules):
- Define TypeScript spec → codegen creates native interface
- Supports synchronous methods (via JSI)
- Lazy-loaded — only initialized when first used
- Type-safe across the JS-Native boundary
When to write native vs stay in JS:
| Need | Approach |
|---|---|
| File system access | Native module (or expo-file-system) |
| Bluetooth | Native module (platform-specific APIs) |
| Image manipulation | Native module (hardware-accelerated) |
| Encryption | Native module (platform keychain/keystore) |
| Complex animations | Reanimated worklets (stay in JS) |
| API calls | JS (fetch/axios) — no native needed |
| Data transformation | JS — unless processing >100MB |
💻 Code Example
1// === TURBOMODULE: Full Example ===23// 1. TypeScript Spec (shared between platforms)4// specs/NativeSecureStorage.ts5import type { TurboModule } from 'react-native';6import { TurboModuleRegistry } from 'react-native';78export interface Spec extends TurboModule {9 // Synchronous — uses iOS Keychain / Android Keystore10 setItem(key: string, value: string): boolean;11 getItem(key: string): string | null;12 removeItem(key: string): boolean;13 getAllKeys(): string[];1415 // Async for potentially slow operations16 clear(): Promise<void>;17}1819export default TurboModuleRegistry.getEnforcing<Spec>('SecureStorage');2021// 2. Android Implementation (Kotlin)22/*23package com.myapp.modules2425import com.facebook.react.bridge.*26import com.facebook.react.module.annotations.ReactModule27import android.security.keystore.KeyGenParameterSpec28import java.security.KeyStore2930@ReactModule(name = SecureStorageModule.NAME)31class SecureStorageModule(reactContext: ReactApplicationContext)32 : NativeSecureStorageSpec(reactContext) {3334 companion object {35 const val NAME = "SecureStorage"36 }3738 private val prefs by lazy {39 // EncryptedSharedPreferences for secure storage40 EncryptedSharedPreferences.create(41 "secure_prefs",42 MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),43 reactApplicationContext,44 EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,45 EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM46 )47 }4849 override fun getName() = NAME5051 override fun setItem(key: String, value: String): Boolean {52 return try {53 prefs.edit().putString(key, value).commit()54 } catch (e: Exception) {55 false56 }57 }5859 override fun getItem(key: String): String? {60 return prefs.getString(key, null)61 }6263 override fun removeItem(key: String): Boolean {64 return prefs.edit().remove(key).commit()65 }6667 override fun getAllKeys(): WritableArray {68 val keys = Arguments.createArray()69 prefs.all.keys.forEach { keys.pushString(it) }70 return keys71 }7273 override fun clear(promise: Promise) {74 try {75 prefs.edit().clear().commit()76 promise.resolve(null)77 } catch (e: Exception) {78 promise.reject("CLEAR_ERROR", e.message)79 }80 }81}82*/8384// 3. iOS Implementation (Swift)85/*86@objc(SecureStorage)87class SecureStorage: NSObject, NativeSecureStorageSpec {8889 @objc func setItem(_ key: String, value: String) -> Bool {90 let data = value.data(using: .utf8)!91 let query: [String: Any] = [92 kSecClass as String: kSecClassGenericPassword,93 kSecAttrAccount as String: key,94 kSecValueData as String: data,95 ]96 SecItemDelete(query as CFDictionary) // Remove existing97 let status = SecItemAdd(query as CFDictionary, nil)98 return status == errSecSuccess99 }100101 @objc func getItem(_ key: String) -> String? {102 let query: [String: Any] = [103 kSecClass as String: kSecClassGenericPassword,104 kSecAttrAccount as String: key,105 kSecReturnData as String: true,106 ]107 var result: AnyObject?108 let status = SecItemCopyMatching(query as CFDictionary, &result)109 guard status == errSecSuccess, let data = result as? Data else { return nil }110 return String(data: data, encoding: .utf8)111 }112}113*/114115// 4. Usage in JS — same API regardless of platform116import SecureStorage from './specs/NativeSecureStorage';117118// Synchronous access (JSI-powered)119const token = SecureStorage.getItem('auth_token');120if (token) {121 api.setAuthHeader(token);122}123124// Store new token125const success = SecureStorage.setItem('auth_token', newToken);126if (!success) {127 ErrorReporter.report(new Error('Failed to store auth token'));128}
🏋️ Practice Exercise
Native Module Exercises:
- Write a TurboModule spec for a device information module (battery level, device model, OS version)
- Implement a native module that accesses the platform's biometric authentication (Face ID / Fingerprint)
- Create a native module that wraps a third-party SDK (e.g., a payment provider)
- Build a native module that performs heavy image processing (resize, crop) off the JS thread
- Implement a platform-specific module with different behavior on iOS vs Android using the same JS API
- Write comprehensive tests for a native module — mock the native side and test the JS interface
⚠️ Common Mistakes
Not handling errors in native modules — native exceptions that propagate to JS cause cryptic crashes
Running heavy operations on the main thread in native code — blocks UI just like blocking the JS thread
Forgetting to handle the case where the native module is not available — always check for null from TurboModuleRegistry
Not properly cleaning up native resources — event listeners, timers, and callbacks in native code must be deallocated
Using the old bridge-based NativeModules API in new projects — always use TurboModules with codegen for new modules
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Writing Native Modules (Android & iOS). Login to unlock this feature.