How to write Native Modules for React Native

Sometimes, we have to access native platform APIs that are not available in Javascript.

For example, the integration of some custom hardware devices with our application using Bluetooth. In this case, we have to use native API code for the Bluetooth module for both platforms, Java/Kotlin for Android and Objective-C/Swift for iOS.

There might be a case where you want to use your existing code implementation written in Objective-C, Java, Kotlin, or Swift in your React Native application, or you want to write some high-performance, multi-threaded code for things like video processing, machine learning, etc.

The Native Module system allows us to use native APIs code written in Java, Kotlin, Objective-C, and Swift with JS code. The native module works as a bridge between native APIs and React Native code, allowing us to run native APIs code within JS, send data to native APIs, and receive data from native APIs.

In this article, we will build the architecture to implement native modules in React Native.

Outline

These are the main steps that we are going to implement in the article.

  • Creating React Native Project – We will create a new React Native project for consuming native API code.
  • Creating Native Module – We will create a native module in Android and iOS projects one after another.
  • Exposing Native Module – We will expose the native module written in native code to react-native (JS) code.
  • Registering the Module (Android) – We will register the native module for Android.
  • Testing –  We will test out the exported native modules in our react-native application.

Let’s get it started!

1. Create a React Native Project

First of all, we have to create a React Native project. If you have an existing react-native project, you can start with it.

To  create one, run the command in the terminal:

react-native init NativeModuleDemo

We will integrate the native module in iOS first. Your React Native project should have ‘iOS’ and  ‘android’ folders in the react-native project directory. Let’s open the iOS project workspace in XCode.

Creating Native Module (iOS)

For this article we will write native code in Swift, so let’s begin by creating a Swift file in Xcode in your project.

Let’s name it ‘NativeModuleManager’, you can name it whatever you want. When you have the swift file, it will prompt you to create a bridging-header file, so you need to select the ‘Create Bridging Header’ option. XCode creates the bridging file for code communication between Objective-C & swift.

Let’s add a header line in Bridging-Header.h file.

#import "React/RCTBridgeModule.h

Native Module Class

Since we have created NativeModuleManager.swift, let’s add native code in it for communication with React Native code. Whatever class we want to communicate with react-native, we must inherit it from NSObject, to expose it to Obj-C.

NativeModuleManager will look something like this:

@objc (NativeModuleManager)
class NativeModuleManager : NSObject {

@objc
func constantsToExport() -> [AnyHashable : Any]! {
    return ["message": “Hello from native code“]
}

}

Exposing Native Modules

We have implemented the class that will communicate with React Native, but haven’t exposed the class to React Native yet. So now we expose the code using Obj-C macros provided by React Native.

To use macro, we will create a new objective-c class and name the file ‘NativeModuleManager.m’. We have to import RCTBridgeModule to use the macros for native and react-native code bridging.

// NativeModuleManager.m

#import "React/RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(NativeModuleManager, NSObject)
@end

RCT_EXTERN_MODULE macro is used to expose the native class to react-native, the first argument is the name of a class that we want to expose to react-native and the second argument is the superclass.

Accessing Native Modules

We have set up basic things in the iOS project, now let’s move to the react-native project for consuming native code.

Consuming the native module is the simplest thing, we just have to import NativeModules from react-native. All our exposed code would be accessible in React native.

In App.js, let’s import NativeModules and console it, to check whether the exposed code is accessible here.

// App.js

import { NativeModules } from 'react-native'

console.log(NativeModules.NativeModuleManager)

It will log returned static data like this:

"message": “Hello from native code“

At this point, we managed to call our native code written in Swift from react-native and to get some data from the native project.

Exposing Native Modules (Methods)

We will expose two methods to react-native code. In one scenario, we just want to call a method to do some native processing, and in another, we want a response back after the completion of the code. Let’s implement one by one.

// NativeModuleManager.swift

@objc (NativeModuleManager)
class NativeModuleManager : NSObject {

// Calling a routine & exporting static data
@objc
func constantsToExport() -> [AnyHashable : Any]! {
    return [
        "number": 70.0,
        "string": "hello from static data",
        "bool": true,
    ]
}

// Just calling a routing
@objc
func doSomethingInNative() {
    print("Hi, I'm written in Native code, and being consumed in react-native code.")
}

// Calling a routine & get response back
@objc
func doSomethingGiveBack(_ callback: RCTResponseSenderBlock) {
    callback(["Hi, I'm written in Native code, and being consumed in react-native code."])
}

}

As we are returning the array in the callback, we can send multiple data items back in response.

Let’s expose methods to react-native.

// NativeModuleManager.m

#import "React/RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(NativeModuleManager, NSObject)

RCT_EXTERN_METHOD(doSomethingInNative)
RCT_EXTERN_METHOD(doSomethingGiveBack: (RCTResponseSenderBlock)callback)
@end

RCT_EXTERN_METHOD macro is used to expose the native class function to react-native.

Now, let’s consume in react-native.

// App.js

// Access static data
console.log(NativeModules.NativeModuleManager)

// Calling a method
NativeModules.NativeModuleManager.doSomethingInNative()

// Calling a rounte & get response back
NativeModules.NativeModuleManager.doSomethingGiveBack(message => {
        console.log(message)
})

Note: You might get a warning saying that you didn’t implement the requiresMainQueueSetup method. This happens when we use constantsToExport or in React Native v0.49+.

To remove this warning, add the code in the class that you have exposed to react-native.

// NativeModuleManager.swift

@objc
    static func requiresMainQueueSetup() -> Bool {
        return true
}

2. Creating Native Module (Android)

The next major step is integrating the native module into Android.

Your react-native project should have an ‘android’ folder in the react-native project directory. Let’s open the Android project in Android Studio.

We will write native code in Kotlin, so create a kotlin file in Android Studio for your project.

We named it ‘NativeModuleManager’.

For working in Kotlin, we have to add configuration lines in the build.grade file. Add bold lines in the build.grad file.

buildscript {
    ext.kotlin_version = '1.4.30'
    ext {
        buildToolsVersion = "29.0.2"
        minSdkVersion = 16
        compileSdkVersion = 29
        targetSdkVersion = 29
    }
    repositories {
        google()
        jcenter()
        maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
    }
    dependencies {
        classpath("com.android.tools.build:gradle:3.5.3")
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
    
allprojects {
    repositories {
        mavenLocal()
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url("$rootDir/../node_modules/react-native/android")
        }
        maven {
            // Android JSC is installed from npm
            url("$rootDir/../node_modules/jsc-android/dist")
        }
        
        google()
        jcenter()
        maven { url 'https://www.jitpack.io' }
    }
}

Native Module Class

Now it’s time to implement the getName method in the class that we want to expose to react-native. This method returns a string, which represents the name of the native module.

NativeModuleManager will look something like this:

// NativeModuleManger.kt

package com.nativemoduledemo

import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod

class NativeModuleManager(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {

    override fun getName(): String {
        return "NativeModuleManager";
    }
}

Exposing Native Modules

We have implemented the class that will communicate with react-native, but haven’t exposed the class to react-native yet. We have to expose the code using annotations provided by react-native.

All native methods, to be invoked in react-native must be annotated with @ReactMethod.

// NativeModuleManager.kt

class NativeModuleManager(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {

    override fun getConstants(): Map<String, Any>? {
    val constants: MutableMap<String, Any> = HashMap()
    constants["message"] = “Hello from native code”
    return constants
  }
}

Registering Native Modules

We have to register the native module with react-native in android. We will add native modules to ReactPackage and then register the ReactPackage with React Native.

During initialization, react-native iterates overall packages and for each ReactPackage it registers the native module.

For Android, to access the native module in react-native, we will instantiate and return it with the createNativeModules method.

To add native modules to react-native, let’s create a new Kotlin class named ‘NativeModuleManagerPackage’. We will implement ReactPackage in this file which will expose our native code to react-native.

// NativeModuleManagerPackage.kt

package com.nativemoduledemo

import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
import java.util.*
import kotlin.collections.ArrayList

class NativeModuleManagerPackage : ReactPackage {
    override fun createViewManagers(p0: ReactApplicationContext): MutableList<ViewManager<View, ReactShadowNode<*>>> {
    return Collections.emptyList()
    }
    
    override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> {
        val modules: MutableList<NativeModule> = ArrayList()
    modules.add(NativeModuleManager(reactContext))
    return modules
    }
}

For registering the NativeModuleManagerPackage, we have to add code in MainApplication.kt as well. The bolded code in `MainApplication` is where we will add our package class which contains a list of native modules.

// MainApplication.kt

package com.nativemoduledemo

import android.content.Context
import com.facebook.react.*
import com.facebook.soloader.SoLoader
import java.lang.reflect.InvocationTargetException

class MainApplication : Application(), ReactApplication {

private val mReactNativeHost: ReactNativeHost = object : ReactNativeHost(this) {
    override fun getUseDeveloperSupport(): Boolean {
        return BuildConfig.DEBUG
    }
    
    override fun getPackages(): List<ReactPackage> {
        val packages: MutableList<ReactPackage> = PackageList(this).packages
        // Packages that cannot be autolinked yet can be added manually here, for example:
        packages.add(NativeModuleManagerPackage())
        return packages
    }
    
    override fun getJSMainModuleName(): String {
        return "index"
    }
}

...

}

Accessing Native Modules

Now that we set up the basic aspects of the Android project,  we can move along with the react-native project for consuming native code.

We just have to import NativeModules from react-native and all our exposed code will be accessible in React native.

In App.js we have to import NativeModules and console it, to check the accessibility of the exposed code.

// App.js

import { NativeModules } from 'react-native'

console.log(NativeModules.NativeModuleManager)

It will log returned static data like this:

"message": “Hello from native code“

Mission accomplished: we are done calling our native code written in Kotlin from react-native and already got some data from the native project.

Exposing Native Modules (Methods)

Here we will expose two methods to react-native code.

In one scenario, we just want to call a routine to do some native processing, and in another, we want a response back after the completion of the code. Let’s implement one by one.

// NativeModuleManager.kt

// Calling a routing & exporting static data

override fun getConstants(): Map<String, Any>? {
    val constants: MutableMap<String, Any> = HashMap()
    constants["number"] = 90
    constants["string"] = "hello from static data"
    constants["bool"] = true
    return constants
}

// Just calling a routing

    @ReactMethod
    fun doSomethingInNative() {
        print("Hi, I'm written in Native code, and being consumed in react-native code.")
    }
    
// Calling a routing & get response back

    @ReactMethod
    fun doSomethingGiveBack(callback: Callback) {
        callback.invoke("Hi, I'm written in Native code, and being consumed in react-native code.")
    }

Conclusions

Implementing native modules in both the Android and iOS projects adds great value to the React Native ecosystem, as we have a grip on native APIs while maintaining a single codebase for Android/iOS platforms.

I hope my article helped you implement native modules in both Android and iOS within your project as well.

The article was originally published on this link.

guest
0 Comments
Inline Feedbacks
View all comments