Sometimes, we have to access native platforms APIs that are not available in Javascript.
For example, the integration of some custom hardware device 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. 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.
These are the main steps that we are going to implement in the article.
Let’s get it started!
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.
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
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, for exposing 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“]
}
}
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.
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.
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 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
}
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 in 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' }
}
}
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";
}
}
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
}
}
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"
}
}
...
}
Now that we set up 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.
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 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.")
}
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.