DEV Community

Ajmal Hasan
Ajmal Hasan

Posted on • Updated on

Mastering Android Native Bridging in React Native

React Native allows you to write native code in Java and access it from JavaScript. This can be particularly useful when you need to access native functionality that isn’t available out of the box in React Native.

Key Concepts

  1. Bridging: Allows JavaScript and native code to communicate.
  2. Native Modules: Java classes that are exposed to JavaScript.
  3. Native UI Components: Native views that can be used in React Native’s JavaScript code.
  4. Events: Mechanism to send data from native code to JavaScript.

Practical Example

Let’s create a simple example where we:

  1. Create a native module in Java.
  2. Call this module from JavaScript.
  3. Pass data from JavaScript to native code and vice versa.
  4. Handle events from native to JavaScript.
  5. Create a native UI component.

ANDROID PART:

Step 1: Create the Native Module Class

First, create a native module in Java(ie Java class).

Create a new Java class in the android/app/src/main/java/com/yourappname directory:



package com.rn_nativemodules;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

// This class serves as a bridge between the native Android code and the React Native JavaScript code
// It extends ReactContextBaseJavaModule, which is a base class for native modules in React Native
public class MyNativeModule extends ReactContextBaseJavaModule {

    // Define the module name that will be used to reference this module in React Native
    private static final String MODULE_NAME ="MyNativeModule";
    private ReactApplicationContext reactContext;

    // Constructor
    public MyNativeModule(@Nullable ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
    }

    // Return the name of the module
    @NonNull
    @Override
    public String getName() {
        return MODULE_NAME;
    }

    // Define native methods that can be called from React Native using @ReactMethod annotation

    // Method to log a message received from React Native
    @ReactMethod
    public void myMethod(String param){
        System.out.println("GET DATA FROM RN <--- "+param);
    }

    // Method to send data back to React Native with a promise
    @ReactMethod
    public void myMethodWithPromise(String param, Promise promise){
        // Check if the parameter is not null
        if (param != null) {
            // Resolve the promise with a success message
            promise.resolve("SEND DATA TO RN ---> " + param);
        } else {
            // Reject the promise with an error code and message
            promise.reject("ERROR_CODE", "Error message explaining failure");
        }
    }

    // You can define more native methods here to be called from React Native
    // For example, adding and removing event listeners

    // Method to send an event to React Native
    private void sendEvent(ReactContext reactContext, String eventName, WritableMap params) {
        // Access the device event emitter module and emit an event with parameters
        reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(eventName, params);
    }

    // Example method triggered from React Native to send an event
    @ReactMethod
    public void triggerEvent() {
        // Create parameters to send with the event
        WritableMap params = new WritableNativeMap();
        params.putString("message", "Hello from native code!");

        // Call the sendEvent method to emit "MyEvent" with params to React Native
        sendEvent(getReactApplicationContext(), "MyEvent", params);
    }

}


Enter fullscreen mode Exit fullscreen mode

Summary:
1. Creating the Native Module:
• Class Definition: Extend ReactContextBaseJavaModule to create a native module.
• Module Name: Define a unique module name using the getName() method.
• Constructor: Initialize the module with ReactApplicationContext.

2. Defining Native Methods:
• Simple Methods: Use @ReactMethod annotation to define methods that can be called from JavaScript.
• Logging: Create methods to log messages from JavaScript.
• Promise Handling: Implement methods that use promises for asynchronous operations, resolving or rejecting based on logic.
• Event Emission: Create methods to send events from native code to JavaScript using DeviceEventManagerModule.RCTDeviceEventEmitter.

3. Example Methods:
• myMethod(String param): Logs a message.
• myMethodWithPromise(String param, Promise promise): Resolves or rejects a promise based on the input.
• triggerEvent(): Emits an event to JavaScript with predefined parameters.


Step 2: Register the Module

Next, you need to register the module in a Package class:



package com.rn_nativemodules;

import androidx.annotation.NonNull;
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.ArrayList;
import java.util.Collections;
import java.util.List;

// This class implements the ReactPackage interface, which is responsible for providing NativeModules and ViewManagers to React Native
// It acts as a package manager for bridging native code with React Native
public class MyNativeModulePackage implements ReactPackage {

    // Method to create and return NativeModules to be used by React Native
    @NonNull
    @Override
    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactApplicationContext) {
        List<NativeModule> modules = new ArrayList<>();

        // Add your custom NativeModule implementations to the list
        modules.add(new MyNativeModule(reactApplicationContext)); // Add MyNativeModule as a NativeModule
        return modules;
    }



Enter fullscreen mode Exit fullscreen mode

Step 3: Add the Package to the Main Application

Add your package to the list of packages in your MainApplication.java:



// android/app/src/main/java/com/yourappname/MainApplication.java
import com.yourappname.MyNativeModulePackage; // Add this import

@Override
protected List<ReactPackage> getPackages() {
    @SuppressWarnings("UnnecessaryLocalVariable")
    List<ReactPackage> packages = new PackageList(this).getPackages();
    packages.add(new MyNativeModulePackage()); // Add this line
    return packages;
}


Enter fullscreen mode Exit fullscreen mode

JAVASCRIPT PART:

Step 1: Import and Use NativeModules



import {
  Button,
  DeviceEventEmitter,
  NativeModules,
  requireNativeComponent,
  StyleSheet,
  View,
} from 'react-native';
import React, {useEffect} from 'react';

const {MyNativeModule} = NativeModules;

// Android Files Modified for NativeModules:
// 1. MyNativeModule.java
// 2. MyNativeModulePackage.java
// 3. MainApplication.java (getPackages())

const AndroidNativeModules = () => {
  // Define a function to send data to the native Android module
  const sendDataToAndroid = () => {
    // Call the native method without using a promise
    MyNativeModule.myMethod('THIS IS SENT FROM RN TO ANDROID');
  };

  // Define a function to get data from the native Android module
  const getDataFromAndroid = () => {
    // Call the native method with a promise
    MyNativeModule.myMethodWithPromise('Sent From RN To A then from A to RN')
      // Handle the resolved promise (success case)
      .then(result => {
        // Log the success result
        console.log('Success:', result);
      })
      // Handle the rejected promise (error case)
      .catch(error => {
        // Log the error information
        console.error('Error:', error.code, error.message);
      });
  };

// To handle events, we need to use RCTDeviceEventEmitter on the native side and DeviceEventEmitter on the JavaScript side.
  useEffect(() => {
    const subscription = DeviceEventEmitter.addListener('MyEvent', params => {
      console.log('Received event with message:', params.message);
    });

    // Clean up subscription
    return () => {
      subscription.remove();
    };
  }, []);

  const triggerNativeEvent = () => {
    MyNativeModule.triggerEvent();
  };

  return (
    <View style={styles.main}>
      <Button
        title="Send Data From RN To Android"
        onPress={sendDataToAndroid} // check log in android logcat 
      />
      <Button
        title="Send Data To Android From RN"
        onPress={getDataFromAndroid} // check log in react native logcat 
      />
      <Button
        title="Receive event From RN From Android"
        onPress={triggerNativeEvent}
      />
    </View>
  );
};

export default AndroidNativeModules;

const styles = StyleSheet.create({
  main: {justifyContent: 'space-evenly', alignItems: 'center', flex: 1},
});



Enter fullscreen mode Exit fullscreen mode



Creating a Native UI Component

Creating a native UI component involves creating a ViewManager.

Step 1: Create the ViewManager



package com.rn_nativemodules;

import android.widget.TextView;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;

public class MyCustomViewManager extends SimpleViewManager<TextView> {

public static final String REACT_CLASS = "MyCustomView";

@Override
public String getName() {
    return REACT_CLASS;
}

@Override
protected TextView createViewInstance(ThemedReactContext reactContext) {
    return new TextView(reactContext);
}

@ReactProp(name = "text")
public void setText(TextView view, String text) {
    view.setText(text);
}
Enter fullscreen mode Exit fullscreen mode

}

Enter fullscreen mode Exit fullscreen mode




Step 2: Register the ViewManager




// android/app/src/main/java/com/yourappname/MyPackage.java

// Existing code...

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> viewManagers = new ArrayList<>();
viewManagers.add(new MyCustomViewManager());
return viewManagers;
}

Enter fullscreen mode Exit fullscreen mode




Step 3: Use the Custom View in JavaScript




import { requireNativeComponent } from 'react-native';

const MyCustomView = requireNativeComponent('MyCustomView');

const App = () => {
return (
<MyCustomView style={{ width: 200, height: 50 }} text="Hello from custom view!" />
);
};

export default App;

Enter fullscreen mode Exit fullscreen mode




Final UI:

Image description

iOS Part ->

Top comments (0)