Native Module Development
Core Concepts of Native Modules
Why Native Modules Are Needed
// Example: Scenarios requiring platform-specific functionality
// 1. Accessing device sensors (e.g., Bluetooth, NFC)
// 2. Using platform-specific APIs (e.g., Android’s JobScheduler)
// 3. Integrating third-party native SDKs
// 4. Implementing high-performance computing or graphics processing
Android Native Module Development
Creating a Simple Native Module
Step 1: Create the Native Module Class
// android/app/src/main/java/com/example/app/ToastModule.java
package com.example.app;
import android.widget.Toast;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class ToastModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext reactContext;
public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
// Required method: Returns the module name
@Override
public String getName() {
return "ToastModule";
}
// Method exposed to JavaScript
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(reactContext, message, duration).show();
}
}
Step 2: Create the Package Manager Class
// android/app/src/main/java/com/example/app/ToastPackage.java
package com.example.app;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ToastPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ToastModule(reactContext)); // Add our module
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList(); // No view managers needed
}
}
Step 3: Register the Package in the Main Application
// android/app/src/main/java/com/example/app/MainApplication.java
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ToastPackage() // Add our package
);
}
Step 4: Use in JavaScript
// ToastExample.js
import { NativeModules } from 'react-native';
const { ToastModule } = NativeModules;
export default {
showToast(message, duration = ToastModule.SHORT) {
ToastModule.show(message, duration);
}
};
// Usage example
import ToastExample from './ToastExample';
ToastExample.showToast('Hello from Android Native!', ToastModule.LONG);
Adding Constants to JavaScript
// In ToastModule.java
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("SHORT", Toast.LENGTH_SHORT);
constants.put("LONG", Toast.LENGTH_LONG);
return constants;
}
iOS Native Module Development
Creating a Simple Native Module
Step 1: Create the Native Module Class
// ios/YourApp/ToastModule.m
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(ToastModule, NSObject)
// Method exposed to JavaScript
RCT_EXTERN_METHOD(show:(NSString *)message duration:(NSInteger)duration)
@end
// ios/YourApp/ToastModule.h
#import <React/RCTBridgeModule.h>
@interface ToastModule : NSObject <RCTBridgeModule>
@end
Implementation File (Using Objective-C++)
// ios/YourApp/ToastModule.mm
#import "ToastModule.h"
#import <UIKit/UIKit.h>
@implementation ToastModule
RCT_EXPORT_MODULE();
// Implement the exported method
RCT_EXPORT_METHOD(show:(NSString *)message duration:(NSInteger)duration) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Toast"
message:message
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alertView show];
// iOS 9+ Use UIAlertController
if (@available(iOS 9.0, *)) {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:@"Toast"
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction
actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:nil];
[alertController addAction:okAction];
[[UIApplication sharedApplication].keyWindow.rootViewController
presentViewController:alertController
animated:YES
completion:nil];
}
});
}
@end
Step 2: Use in JavaScript
// ToastExample.js
import { NativeModules } from 'react-native';
const { ToastModule } = NativeModules;
export default {
showToast(message, duration = 2000) {
// iOS does not have predefined constants; handle manually
ToastModule.show(message, duration);
}
};
// Usage example
import ToastExample from './ToastExample';
ToastExample.showToast('Hello from iOS Native!');
Adding Constants to JavaScript
// In ToastModule.m
RCT_EXTERN_METHOD(getConstants:(RCTResponseSenderBlock)callback)
@implementation ToastModule
RCT_EXPORT_MODULE();
- (NSDictionary *)constantsToExport {
return @{
@"SHORT": @0,
@"LONG": @1
};
}
// Or use callback approach (older API)
RCT_EXTERN_METHOD(getConstants:(RCTResponseSenderBlock)callback) {
callback(@[@{@"SHORT": @0, @"LONG": @1}]);
}
@end
Type Conversion and Data Passing
Basic Data Type Conversion
// Android Java type conversion example
@ReactMethod
public void processData(String stringParam, int intParam, boolean boolParam, Double doubleParam) {
// Process parameters...
}
// iOS Objective-C type conversion example
RCT_EXPORT_METHOD(processData:(NSString *)stringParam
intParam:(NSInteger)intParam
boolParam:(BOOL)boolParam
doubleParam:(double)doubleParam) {
// Process parameters...
}
Array and Object Passing
// Android passing array
@ReactMethod
public void processArray(ReadableArray array) {
for (int i = 0; i < array.size(); i++) {
String item = array.getString(i);
// Process array item...
}
}
// Android passing object
@ReactMethod
public void processMap(ReadableMap map) {
String name = map.getString("name");
int age = map.getInt("age");
// Process object...
}
// iOS passing array
RCT_EXPORT_METHOD(processArray:(NSArray *)array) {
for (NSString *item in array) {
// Process array item...
}
}
// iOS passing dictionary
RCT_EXPORT_METHOD(processMap:(NSDictionary *)map) {
NSString *name = map[@"name"];
NSNumber *age = map[@"age"];
// Process object...
}
Callback Function Handling
// Android callback example
@ReactMethod
public void fetchData(Callback successCallback, Callback errorCallback) {
try {
// Simulate data fetch
String data = "Sample Data";
successCallback.invoke(data);
} catch (Exception e) {
errorCallback.invoke(e.getMessage());
}
}
// Usage example (JavaScript)
ToastModule.fetchData(
(data) => console.log('Success:', data),
(error) => console.error('Error:', error)
);
// iOS callback example
RCT_EXPORT_METHOD(fetchData:(RCTResponseSenderBlock)successCallback
errorCallback:(RCTResponseSenderBlock)errorCallback) {
@try {
// Simulate data fetch
NSString *data = @"Sample Data";
successCallback(@[data]);
} @catch (NSException *exception) {
errorCallback(@[exception.reason]);
}
}
// Usage example (JavaScript)
ToastModule.fetchData(
(data) => console.log('Success:', data),
(error) => console.error('Error:', error)
);
Promise Support
// Android Promise example
@ReactMethod
public void fetchData(Promise promise) {
try {
// Simulate data fetch
String data = "Sample Data";
promise.resolve(data);
} catch (Exception e) {
promise.reject("FETCH_ERROR", e);
}
}
// Usage example (JavaScript)
async function getData() {
try {
const data = await ToastModule.fetchData();
console.log('Success:', data);
} catch (error) {
console.error('Error:', error);
}
}
// iOS Promise example
RCT_REMAP_METHOD(fetchData, fetchDataWithResolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
@try {
// Simulate data fetch
NSString *data = @"Sample Data";
resolve(data);
} @catch (NSException *exception) {
reject(@"FETCH_ERROR", exception.reason, nil);
}
}
// Usage example (JavaScript)
async function getData() {
try {
const data = await ToastModule.fetchData();
console.log('Success:', data);
} catch (error) {
console.error('Error:', error);
}
}
Event Emission and Subscription
Android Event Emission
// 1. Create event emitter
public class ToastModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext reactContext;
private final EventEmitter eventEmitter;
public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
this.eventEmitter = new EventEmitter(reactContext);
}
// 2. Implement event emission method
@ReactMethod
public void startTimer() {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
eventEmitter.emit("TimerEvent", "Timer finished!");
}, 5000);
}
// 3. Internal event emitter class
private static class EventEmitter {
private final ReactContext reactContext;
EventEmitter(ReactContext reactContext) {
this.reactContext = reactContext;
}
void emit(String eventName, String message) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, message);
}
}
}
// JavaScript side event subscription
import { NativeEventEmitter, NativeModules } from 'react-native';
const { ToastModule } = NativeModules;
const eventEmitter = new NativeEventEmitter(ToastModule);
useEffect(() => {
const subscription = eventEmitter.addListener('TimerEvent', (message) => {
console.log('Received event:', message);
});
// Start timer
ToastModule.startTimer();
return () => {
subscription.remove(); // Clean up subscription
};
}, []);
iOS Event Emission
// iOS event emission implementation
@implementation ToastModule {
RCTEventEmitter *_eventEmitter;
}
RCT_EXPORT_MODULE();
- (instancetype)init {
self = [super init];
if (self) {
_eventEmitter = [[RCTEventEmitter alloc] init];
}
return self;
}
// Required event emitter method
- (NSArray<NSString *> *)supportedEvents {
return @[@"TimerEvent"];
}
- (void)startTimer {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self sendEventWithName:@"TimerEvent" body:@"Timer finished!"];
});
}
// Export event emitter method
RCT_EXPORT_METHOD(startTimer) {
[self startTimer];
}
@end
// JavaScript side event subscription (iOS)
import { NativeEventEmitter, NativeModules } from 'react-native';
const { ToastModule } = NativeModules;
const eventEmitter = new NativeEventEmitter(ToastModule);
useEffect(() => {
const subscription = eventEmitter.addListener('TimerEvent', (message) => {
console.log('Received event:', message);
});
// Start timer
ToastModule.startTimer();
return () => {
subscription.remove(); // Clean up subscription
};
}, []);
Debugging and Testing Native Modules
Android Debugging Tips
// 1. Set breakpoints in Android Studio
// 2. Use Logcat to view logs
Log.d("ToastModule", "show method called with message: " + message);
// 3. Debug React Native app
// - Run react-native run-android
// - Attach debugger in Android Studio
// 4. Verify native module registration
// - Check getPackages() method in MainApplication.java
// - Ensure ToastPackage is added to the returned list
iOS Debugging Tips
// 1. Set breakpoints in Xcode
// 2. Use NSLog to view logs
NSLog(@"ToastModule show method called with message: %@", message);
// 3. Debug React Native app
// - Run react-native run-ios
// - Attach debugger in Xcode
// 4. Verify native module registration
// - Check getPackages method in MainApplication.m
// - Ensure ToastPackage is added to the returned array
JavaScript Side Testing
// 1. Unit testing native modules (using Jest)
import { NativeModules } from 'react-native';
const { ToastModule } = NativeModules;
describe('ToastModule', () => {
it('should have show method', () => {
expect(typeof ToastModule.show).toBe('function');
});
// Note: Actual calls to native methods require mocking
// since Jest cannot directly test native code
});
// 2. Integration testing
// - Use React Native Testing Library to test components
// - Mock native module behavior
// 3. E2E testing
// - Use Detox for end-to-end testing
Performance Optimization and Best Practices
Performance Optimization Tips
// Android performance optimization example
@ReactMethod
public void processDataBatch(ReadableArray data, Promise promise) {
// 1. Use background thread for time-consuming operations
new Thread(() -> {
try {
// Simulate time-consuming processing
Thread.sleep(1000);
// Process data
StringBuilder result = new StringBuilder();
for (int i = 0; i < data.size(); i++) {
result.append(data.getString(i)).append("\n");
}
// Return result
promise.resolve(result.toString());
} catch (Exception e) {
promise.reject("PROCESS_ERROR", e);
}
}).start();
}
// iOS performance optimization example
RCT_EXPORT_METHOD(processDataBatch:(NSArray *)data
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
// 1. Use GCD background queue for time-consuming operations
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@try {
// Simulate time-consuming processing
[NSThread sleepForTimeInterval:1.0];
// Process data
NSMutableString *result = [NSMutableString string];
for (NSString *item in data) {
[result appendFormat:@"%@\n", item];
}
// Return result
resolve(result);
} @catch (NSException *exception) {
reject(@"PROCESS_ERROR", exception.reason, nil);
}
});
}
Advanced Topics
TurboModules (New Architecture)
// Android TurboModules example (requires React Native 0.68+)
// 1. Define interface
public interface ToastModuleSpec extends TurboModule {
void show(String message, int duration);
}
// 2. Implement interface
public class ToastModule extends NativeToastModuleSpec implements ToastModuleSpec {
// Implement methods...
}
// 3. Register module (using code generation)
// No manual registration needed in MainApplication.java
// Requires build.gradle configuration to enable TurboModules
// iOS TurboModules example
// 1. Define interface (using code generation)
// ToastModule.h
#import <React/RCTTurboModule.h>
@interface ToastModule : NSObject <RCTTurboModule>
- (void)show:(NSString *)message duration:(NSInteger)duration;
@end
// 2. Implement interface
// ToastModule.mm
@implementation ToastModule
RCT_EXPORT_MODULE()
- (void)show:(NSString *)message duration:(NSInteger)duration {
// Implementation...
}
@end
// 3. Configuration (requires React Native 0.68+)
// Enable TurboModules in Podfile
JSI (JavaScript Interface)
// Example: Using JSI for high-performance native modules
#include <jsi/jsi.h>
#include <string>
using namespace facebook;
void installJSIModule(jsi::Runtime &runtime) {
auto showToast = [](jsi::Runtime &runtime,
const jsi::Value &thisValue,
const jsi::Value *arguments,
size_t count) -> jsi::Value {
if (count < 1 || !arguments[0].isString()) {
return jsi::Value::undefined();
}
std::string message = arguments[0].asString(runtime).utf8(runtime);
// In real applications, this can call native APIs to show Toast
__android_log_print(ANDROID_LOG_DEBUG, "JSIModule", "Message: %s", message.c_str());
return jsi::Value::undefined();
};
runtime.global().setProperty(
runtime,
"nativeShowToast",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "nativeShowToast"),
1,
showToast
)
);
}
Native UI Component Development
// Android native UI component example
public class CustomViewManager extends SimpleViewManager<View> {
@Override
public String getName() {
return "CustomView";
}
@Override
protected View createViewInstance(ThemedReactContext reactContext) {
return new View(reactContext);
}
}
// iOS native UI component example
@implementation CustomViewManager
RCT_EXPORT_MODULE()
- (UIView *)view {
return [[UIView alloc] init];
}
@end
Summary of Native Module Development
React Native native module development serves as a bridge between JavaScript and native platform capabilities, enabling developers to access platform-specific functionality and achieve high-performance operations. Below is a summary of key points:
- Core Concepts:
- Native modules allow JavaScript to call native code (Android Java/Objective-C/Swift).
- Used for accessing platform-specific APIs, integrating third-party SDKs, or implementing high-performance features.
- Requires establishing a communication bridge between JavaScript and native code.
- Development Process:
- Android:
- Create a module class extending
ReactContextBaseJavaModule. - Implement
getName()to define the module name. - Use
@ReactMethodto expose methods to JavaScript. - Create a
ReactPackageand register the module.
- Create a module class extending
- iOS:
- Create a class implementing the
RCTBridgeModuleprotocol. - Use
RCT_EXPORT_MODULE()macro to define the module. - Use
RCT_EXPORT_METHODmacro to expose methods. - Register the module in
MainApplication.m.
- Create a class implementing the
- Android:
- Data Passing:
- Supports basic data types (String, Number, Boolean).
- Supports array and object passing.
- Uses callbacks or Promises for asynchronous operations.
- Uses event emitters for JavaScript to subscribe to native events.
- Performance Optimization:
- Execute time-consuming operations in background threads.
- Minimize frequent JavaScript-native communication.
- Use batch processing to reduce bridge calls.
- Consider TurboModules or JSI for improved performance.
- Debugging and Testing:
- Use platform-specific debugging tools (Android Studio, Xcode).
- Perform unit testing on the JavaScript side (mocking native modules).
- Conduct integration testing to verify interactions.
- Consider end-to-end testing for complete workflows.
- Best Practices:
- Follow platform-specific design patterns.
- Provide clear documentation and examples.
- Handle errors and exceptions gracefully.
- Ensure version compatibility and backward compatibility.
- Optimize memory usage and resource management.
- Advanced Topics:
- TurboModules (new architecture) offer better type safety and performance.
- JSI enables high-performance modules at the C++ layer.
- Native UI component development extends React Native’s UI capabilities.
- Sharing native code libraries improves development efficiency.
By mastering native module development, developers can leverage React Native’s cross-platform advantages while accessing platform-specific features when needed, building feature-rich, high-performance mobile applications.



