Skip to content

Demystifying Building Native Modules for React Native

April 17, 2020Kamile Matulenaite8 min read

You’ve just released a new native SDK. Could you bring more business to your company by releasing on a cross-platform development framework?

Maybe you’re a developer, longing for a native module of an open-source platform API (or even the third-party SDK above 🙄) to be released. Could you fast-track the development yourself?

You could build your own module. With React Native, this becomes a straightforward process.

Let’s talk about React Native for a second.

Why React Native

For those unfamiliar with React Native: it is a cross-platform development framework allowing you to create native apps for iOS and Android. Using React, you can maintain two platforms using the same codebase, allowing faster development, iteration and knowledge-sharing.

With this framework, we have two sides; JavaScript and Native. Between the two is a bridge, allowing bidirectional and asynchronous communication. This is the power of React Native, on top of a multitude of other benefits.

In 2020, we see the likes of Facebook, Bloomberg and Shopify [1] using React Native to develop their mobile applications, amongst others in the Fortune 500. With over 11 million Javascript developers [2] and more companies switching to React Native, releasing your technology for React Native could bring more growth.

We’ve seen other cross-platform development frameworks like Flutter and Xamarin climb the ranks over the last few years, but React Native is still gaining popularity and ever-increasing its performance as it gets more mature.

So hopefully that's convinced you to consider releasing a Native Modules for React Native if you're releasing Native SDKs. Let’s break down how you’d do that.

Native to React Native

As previously mentioned, communication from the native world to React Native is asynchronous. What this means is that any values must be sent through asynchronous callbacks, promises and events, each sent on a batched message queue. This is the architecture as of April 2020; there is currently a re-architecture of the React Native internals and due to be released mid-2020 [3].

rn-bridge-architecture-diagram.png

On the native side:

  • The main thread is responsible for the UI.
  • The shadow queue is responsible for layout calculations.
  • Each native module has its own thread (Android shares a thread pool)

On the JavaScript side:

  • JavaScript VM thread which runs the bundled JS code and sends instructions to the native threads via the bridge.

The two sides communicate over the bridge using the message queue.

JavaScript knows about your native modules at runtime - there’s a JSON representation of each Native Module (consisting of module id, method id and arguments) and we can call methods on the Native Module this way.

For the other direction, Native to JavaScript, we can use promises, callbacks and event to transfer data.

To read more about the React Native internals and the upcoming re-architecture, read this article [4].

Module structure

To get started building your native module, there are a number of tools to help set up the skeleton of your project. For example:

We like Bob, as your library will then come pre-configured with TypeScript and support for Kotlin and Swift. If you've got any other favourites, let us know!

For project structure, if you’ve chosen Bob, the library should now have the following project structure:

- /ios
- /android
- /src
    - index.tsx
- /yourExampleApp
    - package.json
    - ...
- ...

In the iOS and Android folders, you’ll want to have your native SDK code - either available in your project or using git submodules to keep the version control.

Exposing iOS Modules

On the iOS Side, there are two languages at play: Objective-C and Swift. You can write the native module entirely in Objective-C, or write methods in Swift and expose them to Objective-C by using the @objc attribute.

The steps are then as follows:

  1. Create a Bridging Header file (Objective-C, .h file) - Here you'll import:

    #import "React/RCTBridgeModule.h"
    #import <React/RCTLog.h>
    #import <React/RCTEventEmitter.h>
    #import <React/RCTConvert.h>
  2. Create a Bridging Command file (Objective-C, .m file), expose modules with RCT_EXPORT_MODULE and Expose methods with RCT_EXPORT_METHOD

    #import <React/RCTBridgeModule.h>
    #import <React/RCTEventEmitter.h>
    #import "MyModule.h"
    
    @interface RCT_EXTERN_MODULE(MyModule, NSObject)
    
    RCT_EXTERN_METHOD(myMethodWithNoParams)
    
    RCT_EXTERN_METHOD(myMethodWithAPromise:(NSString)input
    resolver:(RCTPromiseResolveBlock)resolve
    rejecter:(RCTPromiseRejectBlock)reject)
    
    + (BOOL)requiresMainQueueSetup
    {
        return YES;
    }
    
    @end
  3. Define event emitters, implementing the Delegate pattern.

    #import <React/RCTBridgeModule.h>
    #import <React/RCTEventEmitter.h>
    #import <React/RCTConvert.h>
    
    @interface RCT_EXTERN_MODULE(MyModuleEmitter, RCTEventEmitter)
    
    // Create a singleton for the EventEmitter class
    
    - (id)allocWithZone:(NSZone *)zone {
     static MyModuleEventEmitter *shared = nil;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
     shared = [super allocWithZone:zone];
     });
     return shared;
     }
    
    - (BOOL)requiresMainQueueSetup
     {
     return YES;
     }
    
    @end

For Swift, a good pattern to use is the Observer-Command-Emitter pattern:

observer-command-emitter-diagram.png

This ties together your observer which implements the delegate, the event emitter which triggers events and the commands which will be the methods to expose to JavaScript. In the above code snippets, you've seen the Objective-C .m files for the above.

Exposing Android Modules

The steps for Android are similar:

  1. Create a ReactPackage to declare the modules to expose:

    package com.mymodule
    
    import java.util.Arrays
    
    import com.facebook.react.ReactPackage
    import com.facebook.react.bridge.NativeModule
    import com.facebook.react.bridge.ReactApplicationContext
    import com.facebook.react.uimanager.ViewManager
    
    class MyPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
    return Arrays.asList<NativeModule>(MyModule(reactContext))
    }
    
       override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
           return emptyList<ViewManager<*, *>>()
       }
    
    }
  2. Create your module extending ReactContextBaseJavaModule to declare the methods to expose using @ReactMethod . Implement listeners to emit events.

    package com.mypackage
    
    import com.facebook.react.bridge._
    import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
    import java.util.\*
    
    enum class MyModuleError(val errorCode: String, val message: String) {
     INPUT_NOT_RECOGNISED( "INPUT_NOT_RECOGNISED", "Input was not of the correct format."),
    }
    
    class MyModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), MyListener {
     companion object {
       const val MODULE_NAME = "MyModule"
     }
    
     override fun getName(): String {
       return MODULE_NAME
     }
    
     private fun createErrorHandler(promise: Promise): (TokenError) -> Unit {
       return fun(error: TokenError) {
         promise.reject(error.errorCode, error.message)
       }
     }
    
     @ReactMethod
     fun myMethodWithNoParams() {
        doSomething();
     }
    
     @ReactMethod
     fun myMethodWithAPromise(input: String, promise: Promise) {
       val handleError = createErrorHandler(promise)
    
       doSomethingWithInputSucceeds(input)?. let {
         promise.resolve("MY_METHOD_SUCCESS")
       } ?: kotlin.run {
         handleError(ModuleError.INPUT_NOT_RECOGNISED)
       }
     }
    
     override fun onEventSuccess(result: String, message: String) {
       val body: WritableMap = Arguments.makeNativeMap(mapOf(
         "result" to result,
         "message" to message
       ))
       this.reactApplicationContext.getJSModule(RCTDeviceEventEmitter::class.java)
         .emit("MyModule/eventSucceeded", body)
     }
    
     override fun onEventFailure(error: String, message: String) {
       val body: WritableMap = Arguments.makeNativeMap(mapOf(
         "error" to error,
         "message" to message
       ))
       this.reactApplicationContext.getJSModule(RCTDeviceEventEmitter::class.java)
         .emit("MyModule/eventFailed", body)
     }
    }

Your new module

Now you’ve exposed your native modules and methods to React Native and can continue to build on top of them. What’s left is to import them from NativeModules from react-native and you’re ready to create your app:

In /src of your Native Module:

import {
  NativeModules,
  DeviceEventEmitter,
  NativeEventEmitter,
} from "react-native";

export const { MyModule, MyModuleEventEmitter } = NativeModules;

Now you're ready to go. Just yarn add react-native-myModule and import the above MyModule and emitters to get going.

To launch your development experience and quality to the next level:

  1. Add a React Native deployment pipeline - https://blog.theodo.com/2019/04/react-native-deployment-pipeline/
  2. Add end-to-end tests with Detox - https://github.com/wix/Detox
  3. Add support for offline - https://dev.to/reactnativeradio/rnr-157-building-great-offline-ready-apps-in-react-native-with-josh-warwick

What a great development set up!

Summary

Once that’s all ready, your module is tested, you’re ready to publish. Very straightforward with npm publish[5].

There you have it. A native module which can now be released to the JavaScript tech community - perhaps bringing in more customers for your business or fast-tracking your next development track.

Hopefully this article gave you an overall idea of where to start building your bridge module. If you have any questions, let us know, we'd be happy to help!

For more resources, the following are some good resources:

Sources + links:

  1. https://reactnative.dev/showcase
  2. https://www.slashdata.co/free-resources/state-of-the-developer-nation-17th-edition?utmsource=BlogPost&utmmedium=Text
  3. https://github.com/react-native-community/discussions-and-proposals/issues/40
  4. https://www.reactnative.guide/3-react-native-internals/3.1-react-native-internals.html
  5. https://docs.npmjs.com/cli/publish
Kamile Matulenaite

Kamile Matulenaite

Architect Developer at Theodo