Working with Native Modules
1. Understanding Native Module Bridge
The React Native Native Module Bridge is the communication layer that allows JavaScript code to interact with native iOS and Android modules. It enables developers to access device-specific features that aren't available through the standard React Native API.
| Component | Description | Usage |
|---|---|---|
| NativeModule | JavaScript interface to native code | Used via NativeModules object |
| RCTBridge | Core bridge object managing communication | Automatically managed by React Native |
| RCTManager | Manages native modules and their methods | Used in native code to register methods |
Example: Accessing a Native Module
import { NativeModules } from 'react-native';
const { MyNativeModule } = NativeModules;
// Call a native method
MyNativeModule.fetchData((error, result) => {
if (error) {
console.error(error);
} else {
console.log(result);
}
});
2. Importing Native Modules
| Module Type | Description | Import Method | Notes |
|---|---|---|---|
| iOS Native Module | Custom native module written in Objective-C or Swift for iOS | import NativeModule from 'react-native' |
Requires native code compilation and linking |
| Android Native Module | Custom native module written in Java or Kotlin for Android | import NativeModule from 'react-native' |
Requires native code compilation and linking |
| Third-party Native Module | Pre-built native modules from external libraries | import { NativeModule } from 'third-party-library' |
May require additional setup or configuration |
Example: Importing a Native Module
import { NativeModules } from 'react-native';\n\nconst { MyNativeModule } = NativeModules;\nconsole.log(MyNativeModule);
3. Calling Native Methods
| Method | Description | Example |
|---|---|---|
| NativeModules | Object containing all registered native modules | const { MyNativeModule } = NativeModules; |
| MyNativeModule.methodName() | Call a method defined in the native module | MyNativeModule.fetchData(); |
Example: Calling a Native Method
import { NativeModules } from 'react-native';
const { MyNativeModule } = NativeModules;
MyNativeModule.fetchData((error, result) => {
if (error) {
console.error('Error fetching data:', error);
} else {
console.log('Data received:', result);
}
});
4. Handling Native Events
| Event Type | Description | Platform Support | Notes |
|---|---|---|---|
| RCTDeviceEventEmitter | Emits device events like 'keyboardWillShow' | IOS, Android | Used to send events from native to JS |
| NativeEventEmitter | Class for handling events from native modules | IOS, Android | More flexible than RCTDeviceEventEmitter |
Example: Listening to Native Events
import { NativeEventEmitter, NativeModules } from 'react-native';\n\nconst { MyNativeModule } = NativeModules;\nconst eventEmitter = new NativeEventEmitter(MyNativeModule);\n\nconst eventListener = eventEmitter.addListener('onEvent', (event) => {\n console.log('Received event:', event);\n});\n\n// Remove listener when done\neventListener.remove();
componentWillUnmount or useEffect to prevent memory leaks.
Event Handling Best Practices
- Always clean up event listeners to prevent memory leaks
- Use
NativeEventEmitterfor better type safety and flexibility - Handle events in a centralized way to avoid cluttering components
5. Creating Custom Native Modules
Custom native modules allow you to extend React Native's functionality by writing platform-specific code in Objective-C/Swift for iOS or Java/Kotlin for Android.
| Platform | Language | Description |
|---|---|---|
| iOS | Objective-C / Swift | Use RCTBridgeModule protocol to expose methods to JavaScript |
| Android | Java / Kotlin | Implement ReactContextBaseJavaModule to bridge native code with JS |
Example: iOS Native Module
@implementation MyCustomModule
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(sayHello:(NSString *)name
withCallback:(RCTResponseSenderBlock)callback)
{
NSString *greeting = [NSString stringWithFormat:@"Hello %@!", name];
callback(@[@{"message": greeting}]);
}
@endExample: Android Native Module
public class MyCustomModule extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "MyCustomModule";
}
@ReactMethod
public void sayHello(String name, Callback callback) {
String greeting = "Hello " + name + "!";
callback.invoke(greeting);
}
}
| Feature | iOS | Android | Notes |
|---|---|---|---|
| Module Registration | Using RCT_EXPORT_MODULE() |
Implementing getPackages() in MainApplication.java |
Required for module access from JS |
| Method Exporting | RCT_EXPORT_METHOD() |
@ReactMethod annotation |
Allows JS to call native methods |
| Async Support | Uses dispatch_async |
Uses AsyncTask or ExecutorService |
For non-blocking operations |
6. Creating Custom Native Modules (Android Kotlin/Java)
Creating custom native modules in React Native allows you to access platform-specific features that aren't available through JavaScript. For Android, you can implement these modules using Kotlin or Java.
1. Setting Up the Native Module
To create a custom native module for Android, you need to:
- Implement a Java or Kotlin class extending
ReactContextBaseJavaModule. - Register the module with React Native's bridge.
2. Example: A Simple Native Module in Kotlin
Below is an example of a simple native module that logs messages to the Android Log.
Example: Logging Module
package com.yourapp
import android.util.Log
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
class LogModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String {
return "LogModule"
}
@ReactMethod
fun logMessage(message: String) {
Log.d("ReactNative", message)
}
}
3. Registering the Module
The module must be registered in your ReactPackage.
Example: ReactPackage Implementation
package com.yourapp
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
import java.util.*
class YourReactPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return Arrays.asList(NativeModule) {
LogModule(reactContext)
}
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager> {
return emptyList()
}
}
4. Using the Module in JavaScript
Once registered, you can use your native module in JavaScript:
Example: Using the Module
import { NativeModules } from 'react-native';
const { LogModule } = NativeModules;
LogModule.logMessage('Hello from React Native!');
5. Handling Asynchronous Operations
For asynchronous operations, you can use callbacks or promises.
Example: Asynchronous Operation
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.ReactMethod
@ReactMethod
fun asyncOperation(callback: Callback) {
// Simulate async operation
Thread {
val result = "Async Result"
reactContext.runOnUiQueueThread {
callback.invoke(result)
}
}.start()
}
6. Table: Module Registration and Configuration
| Configuration | Description | Example |
|---|---|---|
| Module Name | Name used to access the module in JavaScript | LogModule |
| Native Module Class | Class extending ReactContextBaseJavaModule |
LogModule |
| React Package | Class implementing ReactPackage |
YourReactPackage |
7. Best Practices
- Always ensure that the module name is unique to avoid conflicts.
- Use proper error handling in native code and pass errors back to JavaScript if needed.
- Keep the module's interface simple and consistent with React Native patterns.
8. Common Pitfalls
- Not registering the module in
ReactPackagewill result in a runtime error when trying to access it from JavaScript. - Using incorrect method signatures in Kotlin/Java can lead to runtime exceptions or silent failures.
7. Using Native UI Components
React Native provides mechanisms to integrate native UI components into your app, allowing you to leverage platform-specific features and UI elements.
| Component | Description | Platform Support |
|---|---|---|
| MapView | Displays a map with markers and annotations. | iOS, Android |
| WebView | Displays web content within the app. | iOS, Android |
| Slider | Allows users to select a value from a range. | iOS, Android |
| Switch | Toggle switch for boolean values. | iOS, Android |
Example: Using MapView
import { MapView } from 'react-native-maps';
<MapView
style={{ flex: 1 }}
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
/>
Example: Using Slider
import { Slider } from 'react-native';
<Slider
value={value}
minimumValue={0}
maximumValue={100}
onValueChange={(value) => setValue(value)}
/>
8. Implementing Promise-based APIs
| Method | Description | Return Type | Notes |
|---|---|---|---|
| promiseMethod | Asynchronous method returning a Promise | Promise<T> |
Used for non-blocking operations |
| resolve | Function to resolve the Promise | (value: T) => void |
Called when operation completes successfully |
| reject | Function to reject the Promise | (error: Error) => void |
Called when operation fails |
Example: Implementing a Promise-based API
import { Promise } from 'react-native';
const fetchUserData = (userId) => {
return new Promise((resolve, reject) => {
// Simulate async operation
setTimeout(() => {
if (userId && userId > 0) {
resolve({ id: userId, name: 'John Doe' });
} else {
reject(new Error('Invalid user ID'));
}
}, 1000);
});
};
// Usage
fetchUserData(123)
.then(user => console.log(user))
.catch(error => console.error(error));
9. Handling Native Callbacks
| Callback Type | Description | Usage | Notes |
|---|---|---|---|
| successCallback | Function called on successful completion of a native operation | successCallback(result) |
Must be defined in JavaScript to receive result |
| errorCallback | Function called when a native operation fails | errorCallback(error) |
Optional but recommended for error handling |
Example: Using Callbacks in Native Module
import { NativeModules } from 'react-native';
const { MyNativeModule } = NativeModules;
MyNativeModule.performOperation(
(result) => {
console.log('Success:', result);
},
(error) => {
console.error('Error:', error);
}
);
10. Debugging Native Module Issues
Example: Setting up React Native Debugger
// Install React Native Debugger
npm install -g react-native-debugger
// Or use the desktop app to connect to your app
// In your app's dev menu, enable "Debug JS Remotely"
| Issue Type | Description | Solution |
|---|---|---|
| Native Module Not Found | Module fails to load at runtime | Ensure module is linked properly in Xcode/Android Studio |
| TypeScript Errors | Errors in type definitions | Check index.d.ts or use <any> for temporary fix |
| Runtime Crashes | App crashes when calling native method | Use try/catch and log errors via console.error |
Workflow: Debugging Steps
- Enable remote JS debugging in dev menu
- Open React Native Debugger or Chrome DevTools
- Check console logs for native errors
- Use
console.login native code to trace execution - Verify module registration in
NativeModules
react-native link or manual linking is done correctly in MainApplication.java.
#ifdef DEBUG macros for debug-only logic.