Description

One of the many benefits of using the React Native framework is that it does not pigeonhole app development by limiting it to the current platform API or the many third-party libraries that are usually required in order to create and build a project. Sometimes development necessitates for the creation of a module that does not exist either within the platform API or there is no comparable module found within the npm library at npmjs.org. React Native allows developers to create their own module(s) in Native Code (Objective-C & Java for MQ purposes) and bridge them into the project so that the top level JavaScript layer can interact with those modules and use them within that JS layer.

The contents of this page describe what a Native Bridge is within React Native, and how to create one within a project.


What is a Native Bridge?

When you hear the term 'Native Bridge', think of npm packages.

Many (but not all) react-native npm packages that are added/installed into a project are Native Bridges. If the package is a pure JS package, then it is just that - pure JavaScript. If a react-native package contains an `ios` directory and an `android` directory, it is most-likely a Native Bridge. Those two directories are typically where the Native code should live for each platform, which then connects to one or more JS files which should be included within the npm package. Once a package has been added or installed in the project, the developer normally imports the package via those earlier mentioned included JS files into a class within their project, and is then able to interact with the native code to perform some sort of functionality within that class. Some of the more common native elements that are often interacted with are the device camera, photo albums or adding events to calendars.

That whole ability to communicate from the Native module to the top JS layer within the app is what we refer to as a Native Bridge.


Creating a Native Bridge:

For this example we will create a very simply Flash module that checks a device for whether it has a flash or not:

Note: This is not a tutorial in Objective C or Java so I will not spend time explaining what the code does; Only why something is required for bridging purposes.

Step 1: Create the Native iOS code in Objective C

Open the React Native project in XCode as one should not create .h and .m files within PhpStorm of VisualStudio Code or whatever their preferred editor is. Creating these files should occur in XCode.

To create a file, choose the folder where you want the file to live within the project, (good place is where the main.m file resides) and right click, then select New File... and choose either the Header File or Objective-C-File and it will be added to the project. There will be some additional boiler plate options that you can choose, but starting with a blank class is always a good route to go.

Objective C generally requires a header (.h) file and a message (.m) file.

For the flash library we first create the '.h' file which looks like this:

Flash.h

#import <Foundation/Foundation.h>

 

#if __has_include(<React/RCTAssert.h>)

#import <React/RCTBridgeModule.h>

#else

#import "RCTBridgeModule.h"

#endif

 

@interface Flash : NSObject <RCTBridgeModule>

@end

The main take away here is that this header (.h) file implements the RCTBridgeModule protocol, which is required to bridge the native code to the JS code. For completion sake, it defines the Flash class that we are about to implement.

Next we have the message (.m) file and it looks like this:

Flash.m

#import <AVFoundation/AVFoundation.h>

#import "Flash.h"

 

@implementation Flash

 

RCT_EXPORT_MODULE()

 

+ (BOOL)requiresMainQueueSetup

{

  return YES;

}

 

// Check the device for flash capabilities and return callback of success // or fail

RCT_EXPORT_METHOD(hasFlash:(RCTResponseSenderBlock)successCallback errorCallback:(RCTResponseSenderBlock)errorCallback)

{

    AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    if ([device hasTorch] && [device hasFlash]) {

        successCallback(@[]);

    }

    else {

        errorCallback(@[]);

    }

}

@end

React Native will not expose any methods of Flash module to the JS layer unless explicitly told to. This is done using the RCT_EXPORT_METHOD() macro to wrap the hasFlash method of the Flash class.

The code above returns a Promise to the JS layer of either Success or Failure of the code run within the RCT_EXPORT_METHOD wrapper. That is it for the iOS Native code.

Step 2: Create the Native Android code in Java

Open the React Native project in Android Studio as one should not create .java files within PhpStorm of VisualStudio Code or whatever their preferred editor is. PhpStorm will not read the file type properly and you won't have access to proper intellisense.

We now need to drill down to the correct place to add our native Java Flash code. Within the top level android directory open the folders in this path `app → src → main → java →  com.{project name} `

Within this directory, create a new directory and call it 'flash'. It is within this new directory that the flash module Java files will be created.

Right click the 'Flash' directory and select 'New → Java Class' and name it FlashPackage. Then we need to fill it with some code. When we are done it will look like this:

FlashPackage.java

package com.myawesomeproject.flash;

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.Collections;

import java.util.List;

import java.util.ArrayList;

 

public class FlashPackage implements ReactPackage

{

    @Override

    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {

        List<NativeModule> modules = new ArrayList<>();

        modules.add(new FlashModule(reactContext));

        return modules;

    }

    @Override

    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {

        return Collections.emptyList();

    }

}

This Java file implements the RCTBridgeModule protocol, which as we know from before, is required to bridge the native code to the JS code.

Next we have the flash module where the actual logic resides and and it looks like this:

FlashModule.java

package com.myawesomeproject.flash;

import android.content.pm.PackageManager;

 import com.facebook.react.bridge.Callback;

import com.facebook.react.bridge.ReactApplicationContext;

import com.facebook.react.bridge.ReactContextBaseJavaModule;

import com.facebook.react.bridge.ReactMethod;

 

public class FlashModule extends ReactContextBaseJavaModule

{

    /**

     * Member Variables

     */

    private final ReactApplicationContext reactContext;

    /**

     * Constructor

     *

     * @param reactContext ReactApplicationContext

     */

    public FlashModule(ReactApplicationContext reactContext) {

        super(reactContext);

        this.reactContext = reactContext;

    }


    @Override

    public String getName() {

        return "Flash";

    }


    @ReactMethod

    public void hasFlash(Callback successCallback, Callback errorCallback) {

        // Check for whether the device has a flash or not

        if (reactContext.getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {

            successCallback.invoke();

        }

        else {

            errorCallback.invoke();

        }

    }

}

Now that we have these 2 files created, we need to tell the MainApplication.java file to use this newly created Flash module. The MainApplication.java file is located in the parent directory to where we created the flash directory. It should be a sibling to the Flash directory. Open that file and within the import section we would add the following code:

Import

import com.myawesomeproject.flash.FlashPackage;

Next, we need to tell React Native about the package. Within the same MainApplication.java file look for a ReactNativeHost method and add your Flash package. The method should then look similar to this:

Add Flash Package

private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this)

{

    @Override

    public boolean getUseDeveloperSupport() {

        return BuildConfig.DEBUG;

    }


    @Override

    protected List<ReactPackage> getPackages() {

        @SuppressWarnings("UnnecessaryLocalVariable")

        List<ReactPackage> packages = new PackageList(this).getPackages();

        // Packages that cannot be autolinked yet can be added manually

        // here, for example: packages.add(new MyReactNativePackage()); 

        packages.add(new FlashPackage());

        return packages;

    }


    @Override

    protected String getJSMainModuleName() {

        return "index";

    }

};

And with that we now need to move over to the JS layer and create our JavaScript file that will actually bridge the Native layer and the upper level JS layer.

Step 3:  Create the TypeScript or JavaScript file for Native level interactions

We need to create a file that will talk to the RCT_EXPORT_MODULE() in the Native land and any methods to be used in JavaScript land. Now we can safely do this in PhpStorm or whatever code editor is your preference.

You can place this file anywhere you want that makes sense to you in your project. This example is done with TypeScript as that is the standard that we use in MQ projects. For Flash it looks like this:

Flash.ts

/**

 * Imports

 */

import {NativeModules} from "react-native";


/**

 * Native definition and export

 */


// Use Native Code directly

export const Flash: any = NativeModules.Flash;

And with that, we can now import the Flash.ts file and it's method into other files within the project.

An example of what that looks like is this:

Example Usage

import { MQFlash } from "../nativeStuff/Flash";

 

... {Within the class}

    public componentDidMount(): void

    {

        // Perform the check for whether the device has flash capability

        Flash.hasFlash(

            () => {

                // Has flash

                console.log( 'CameraModal::componentDidMount:Flash Detected' );

                this.setState( { hasFlash: true } );

            },

            () => {

                // Does not have flash

                console.log( 'CameraModal::componentDidMount:No Flash Detected' );

                this.setState( { hasFlash: false } );

            }

        );

    } // End of componentDidMount()

...

This implementation should return a Success or Failure and then console.log will display the correct response based on whether the returned promise was a value of success or failure.

And with that, this is what we would call a valid Native Bridge.

For further reading about Native Modules in React Native, see the official ios documentation here and the official android documentation here.



Is Your Business In Need Of Change?
We Can Help.

Initiate Change Now