React Native Bridge for iOS and Android

Written by abhisheknalwaya | Published 2018/12/31
Tech Story Tags: react-native | react | ios | android | javascript

TLDRvia the TL;DR App

One of the biggest reason for the popularity of React Native is that we can create a bridge between the Native language and JavaScript code. Which means we can reuse all the reusable libraries created in iOS and Android world.

To create a production grade application some point of time you need to use Native Bridge. There are very less article and tutorial on cross-platform React Native bridge which works on both iOS and Android. In this article, we will create a Native Bridge to access Swift and Java class from JavaScript.

This is the first part, second post on Native Bridge of UI component can be found here.

The code of this article can be found here -> https://github.com/nalwayaabhishek/LightApp

Create the LightApp

To better understand Native Module we will create a simple LightApp example using react-native CLI.

$ react-native init LightApp$ cd LightApp

Next, we will create a class Bulb in Swift for iOS and Java for Android, and this will be used this app in a React component. This will be cross-platform example and the same React code will work in both iOS and Android.

As we have created a basic skeleton of the project, next we have divided this article into two sections:

Section 1 — Native Bridge in iOS

Section 2 — Native Bridge in Android

Section 1 —Native Bridge iOS

In this section, we will focus on iOS and create a bridge between Swift/Objective C and your React Component. It has these three steps:

Step 1) Create a Bulb class and Bridge Header

Step 2) Understanding GCD Queue and fixing the warning:

Step 3) Accessing a variable in JavaScript from Swift and Callbacks

Step 1) Create a Bulb class and Bridge Header

To get started we will create a Bulb class in swift, which will have a static class variable isOn and a few other functions. And then we will access this swift class from Javascript. Let's start by opening LightApp.xcodeproj file in ios folder. It should open Xcode with your ios code.

Once you open the project in Xcode, then create a new Swift file Bulb.swift as shown:

We have also clicked on Create Bridging Header which will create a file LightApp-Bridging-Header.h This will help to communicate between Swift and Objective C code. Remember that in a project we have only one Bridge Header file. So if we add new files, we can reuse this file.

Update following code in LightApp-Bridging-Header.hfile:

#import "React/RCTBridgeModule.h"

RCTBridgeModule will provide an interface to register a bridge module.

Next update Bulb.swiftwith the following code:

import Foundation

@objc(Bulb)

class Bulb: NSObject {

@objc

static var isOn = **false

**

@objc

func turnOn() {

Bulb.isOn = true

print("Bulb is now ON")

}

}

We have created Bulb class which is inherited from NSObject. The root class of most Objective-C class hierarchies is NSObject, from which subclasses inherit a basic interface to the runtime system and the ability to behave as Objective-C objects. We can see that we have used @objc before a function and class, this will make that class, function or object available to Objective C

The @objc attribute makes your Swift API available in Objective-C and the Objective-C runtime.

Now create a new file from File -> New -> File and select Objective-C file and then name the file as Bulb.m and update following code:

#import "React/RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(Bulb, NSObject)

RCT_EXTERN_METHOD(turnOn)

@end

React Native will not expose any function of Bulb to React JavaScript unless explicitly done. To do so we have used RCT_EXPORT_METHOD() macro. So we have exposed Bulb class and turnOn function to our Javascript code. Since Swift object is converted to Javascript object, there is a type of conversation. RCT_EXPORT_METHOD supports all standard JSON object types:

  • NSString to string
  • NSInteger, float, double, CGFloat, NSNumber to number
  • BOOL to boolean
  • NSArray to array
  • NSDictionary to object with string keys and values of any type from this list
  • RCTResponseSenderBlock to function

Now let’s update JavaScript code and access this Bulb class from our React component. To do so open App.js and update with the following code:

import React, {Component} from 'react';

import {StyleSheet, Text, View, NativeModules, Button} from 'react-native';

export default class App extends Component{

turnOn = () => {

NativeModules.Bulb.turnOn();

}

render() {

return (

<View style={styles.container}>

 <Text style={styles.welcome}>Welcome to Light App!!</Text>

 <Button

    onPress={this.turnOn}

   title="Turn ON "

   color="#FF6347" />

 </View>

);

}

}

const styles = StyleSheet.create({

container: {

flex: 1,

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#F5FCFF',

},

});

Now run the iOS simulator:

Now open Xcode console to see the logs and we can see that Swift turnOn method is called from JavaScript code. (As we have done logging in the method)

Step 2) Understanding GCD Queue and fixing the warning:

Now let's fix the warning shown at the bottom of the simulator and in browser console:

Module Bulb requires main queue setup since it overrides `init` but doesn’t implement `requiresMainQueueSetup`. In a future release React Native will default to initializing all native modules on a background thread unless explicitly opted-out of.

To understand it better let's understand about all the thread React Native runs:

  • Main thread: where UIKit work
  • Shadow queue: where the layout happens
  • JavaScript thread: where your JS code is actually running

Every native module has its own GCD Queue unless it specifies otherwise. Now since this Native module will run on a different thread and our main thread is depended on it, it's showing this warning. And to make this code to run on MainQueue, open Bulb.swift and add this function.

@objc

static func requiresMainQueueSetup() -> Bool {

return true

}

You can explicitly mention return false to run this in separate threat.

Step 3) Accessing a variable in JavaScript from Swift and Callbacks

Now let's add the Bulb Status(ON or OFF) value to our React screen. To do so we will add getStatus function to Bulb.swift and call that method from JavaScript code. We will create this method as a callback.

React Native bridge is asynchronous, so the only way to pass a result to JavaScript is by using callbacks or emitting events

Let's update the code in bold in Bulb.swift

@objc(Bulb)

class Bulb: NSObject {

@objc

static var isOn = false

@objc

func turnOn() {

Bulb.isOn = true

print("Bulb is now ON")

}

@objc

func turnOff() {

Bulb.isOn = false

print("Bulb is now OFF")

}

@objc

func getStatus(_ callback: RCTResponseSenderBlock) {

callback([NSNull(), Bulb.isOn])

}

@objc

static func requiresMainQueueSetup() -> Bool {

return true

}

}

getStatus() method receives a callback parameter that we will pass from your JavaScript code. And we have called the callback with array of values, which will be exposed in JavaScript. We have passed NSNull() as the first element, which we have considered as an error in the callback.

We need to expose this Swift method to JavaScript so following bold lines to Bulb.m:

@interface RCT_EXTERN_MODULE(Bulb, NSObject)

RCT_EXTERN_METHOD(turnOn)

RCT_EXTERN_METHOD(turnOff)

RCT_EXTERN_METHOD(getStatus: (RCTResponseSenderBlock)callback)

@end

We have exposed (RCTResponseSenderBlock)callback as params to the function getStatus

And then finally update the React Code:

import React, {Component} from 'react';

import {StyleSheet, Text, View, NativeModules, Button} from 'react-native';

export default class App extends Component{

constructor(props) {

super(props);

this.state = { isOn: false };

this.updateStatus();

}

turnOn = () => {

NativeModules.Bulb.turnOn();

this.updateStatus()

}

turnOff = () => {

NativeModules.Bulb.turnOff();

this.updateStatus()

}

updateStatus = () => {

NativeModules.Bulb.getStatus( (error, isOn)=>{

this.setState({ isOn: isOn});

})

}

render() {

return (

<View style={styles.container}>

<Text style={styles.welcome}>Welcome to Light App!!</Text>

<Text> Bulb is {this.state.isOn ? "ON": "OFF"}</Text>

{!this.state.isOn ? <Button

onPress={this.turnOn}

title="Turn ON "

color="#FF6347"

/> :

<Button

onPress={this.turnOff}

title="Turn OFF "

color="#FF6347"

/> }

</View>

);

}

}

const styles = StyleSheet.create({

container: {

flex: 1,

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#F5FCFF',

},

});

Rebuild the code and run the app, you can see the Bulb Status value and when you click on Turn ON then it will show Bulb is ON

Do remember to rebuild the code instead of refresh, as we changed Native Code.

Section 2 — Native Bridge in Android

In this section, we will make the same Javascript code which we return for iOS to work with Android. This time we will create Bulb class in Java and expose the same function turnOn, TurnOff ant getStatus to Javascript.

Open Android Studio and click on Open an existing Android Studio project and then select the android folder inside our LightApp. Once all gradle dependency is downloaded, create a Java Class Bulb.java as shown:

And update the following code in Bulb.java:

package com.lightapp;import com.facebook.react.bridge.NativeModule;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.Callback;

public class Bulb extends ReactContextBaseJavaModule {private static Boolean isOn = false;public Bulb(ReactApplicationContext reactContext) {super(reactContext);}

@ReactMethod  
**public void** getStatus(  
    Callback successCallback) {  
    successCallback.invoke(**null**, _isOn_);  

}  

@ReactMethod  
**public void** turnOn() {  
    _isOn_ \= **true**;  
    System.**_out_**.println(**"Bulb is turn ON"**);  
}  
@ReactMethod  
**public void** turnOff() {  
    _isOn_ \= **false**;  
    System.**_out_**.println(**"Bulb is turn OFF"**);  
}  

@Override  
**public** String getName() {  
    **return "Bulb"**;  
}  

}

We have created a Bulb Java class which is inherited from ReactContextBaseJavaModule. ReactContextBaseJavaModule requires that a function called getName is always implemented. The purpose of this method is to return the string name of the NativeModule which represents this class in JavaScript. So here we will call this Bulb so that we can access it through React.NativeModules.Bulb in JavaScript. Instead of Bulb, we can have a different name also.

Not all function is exposed to Javascript explicitly, to expose a function to JavaScript a Java method must be annotated using @ReactMethod. The return type of bridge methods is always void.

We have also created a getStatus function which has params as callback and it returns a callback and passes the value of static variable isOn.

Next step is to register the module, if a module is not registered it will not be available from JavaScript. Create a file by clicking on Menu File -> New -> Java Class and the file name as BulbPackage and then click OK. And then add following code to BulbPackage.java

package com.lightapp;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;

public class BulbPackage implements ReactPackage {@Overridepublic List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {return Collections.emptyList();}

@Override  
**public** List<NativeModule> createNativeModules(  
        ReactApplicationContext reactContext) {  
    List<NativeModule> modules = **new** ArrayList<>();  

    modules.add(**new** Bulb(reactContext));  

    **return** modules;  
}  

}

We need to Override createNativeModules function and add the Bulb object to modules array. If this is not added here then it will not be available in JavaScript.

BulbPackage package needs to be provided in the getPackages method of the MainApplication.java file. This file exists under the android folder in your react-native application directory. Update the following code in android/app/src/main/java/com/LightApp /MainApplication.java

public class MainApplication extends Application implements ReactApplication {...

@Override  
protected List<ReactPackage> getPackages() {  
  return Arrays.<ReactPackage>_asList_(  
      new MainReactPackage(),  
    **  new BulbPackage()**  
  );  
}  

....}

We don’t need to change any JavaScript code written in iOS, as we have exposed the same class name and function. If you have skipped the iOS part then you need to copy the React Javascript code from App.js

Now run the App through Android Studio or from react-native run-android:

Woo!! we can see the Bulb status on the screen and can switch ON or OFF from the button. The great thing is that we have created a Native Bridge which is Cross Platform.

What's Next?

In this article, we have exposed a class method from Swift/Java to JavaScript. In the next article, we have focused on exposing Swift/Java code as UI components.


Published by HackerNoon on 2018/12/31