Skip to main content

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);
  }
});
Note: The bridge is asynchronous by default, and all communication happens on the JavaScript thread.
Warning: Native modules must be registered in the app's native code before they can be used in JavaScript.

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);
Note: Native modules must be properly linked to your project before they can be imported and used.

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);
  }
});
Note: Ensure the native module is properly linked and registered before calling its methods.

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();
Note: Make sure to remove event listeners in componentWillUnmount or useEffect to prevent memory leaks.

Event Handling Best Practices

  • Always clean up event listeners to prevent memory leaks
  • Use NativeEventEmitter for 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}]);
}

@end

Example: 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);
  }
}
Note: Native modules must be registered in the app's main component to be accessible from JavaScript.
Warning: Make sure to handle memory management properly in native code to prevent crashes.
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)

Note: This guide assumes you have a basic understanding of React Native's architecture and are familiar with Android development using Kotlin or 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 ReactPackage will 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)}
/>
Note: Native UI components require platform-specific implementations and may differ in behavior between iOS and Android.

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);
  }
);
Note: Callbacks are synchronous in nature and should be used for immediate results or errors from native operations.

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

  1. Enable remote JS debugging in dev menu
  2. Open React Native Debugger or Chrome DevTools
  3. Check console logs for native errors
  4. Use console.log in native code to trace execution
  5. Verify module registration in NativeModules
Note: On Android, ensure react-native link or manual linking is done correctly in MainApplication.java.
Warning: Avoid modifying native code directly in production builds. Use #ifdef DEBUG macros for debug-only logic.

Was this article helpful?