How to Achieve React Native and iOS Bridging?
We love sharing our learning from the project. Recently, in one of our development projects, there was a need of bridging between iOS or Android and react-native. We were required to integrate the apple map with our application by writing the code in native iOS where the application would interact with the operating system. Once we get the relevant data from apple map, we had to send that data to our JS.
This is a great use case of bridging as you would be able to reuse of iOS or Android code in Javascript (react-native). In this blog, we have only covered the iOS and react native part.
Before going through the examples we need to focus on some macros or libraries:
1) RCTBridgeModule : RCTBridgeModule is a protocol. It provides an interface for registering a bridge module RCTBridgeModule
@protocol RCTBridgeModule
2) RCT_EXPORT_MODULE(js_name): Register your module with the bridge during class implementation. It has an argument js_name which is optional and it is used as the JS module name. In case if it is not defined, Objective-C class name will be defined as the JS module name.
3) RCT_EXPORT_METHOD(method)\RCT_REMAP_METHOD(, method): Bridge modules can also define methods that are exported to JavaScript as NativeModules.ModuleName.methodName
.
Example.,
[js]
RCT_EXPORT_METHOD(funcName:(NSString *)onlyString
secondName:(NSInteger)argInteger)
{ … }
[/js]
This is exposed to JavaScript as `NativeModules.ModuleName.funcName`.
These methods can also return a Promise that are compatible with JS async functions.
Example.,
[js]
RCT_EXPORT_METHOD(funcName:(NSString *)onlyString
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject
{ … }
[/js]
This method has two parameters – resolver and a rejecter block. Calling this method from JS such as `NativeModules.ModuleName.funcName(aString)` will return promise which contains a resolver and rejecter.
Method definition of ‘RCTPromiseResolveBlock’ and ‘RCTPromiseRejectBlock’.
[js]
– typedef void (^RCTPromiseResolveBlock)(id result);
– typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError *error);
[/js]
4) RCT_REMAP_METHOD(js_name, method)\RCT_EXTERN_REMAP_METHOD(js_name, method) : It is similar to RCT_EXPORT_METHOD, but it sets the method name which is exported to the JS.
Example usage:
[js]
RCT_REMAP_METHOD(getThing,
resolver: (RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{ … }
[/js]
This method is available in JS as NativeModules.GetData.getThing()
.
Outlined below are some example to explain this
Example 1
This example contains a text and a button and on clicking the button, the text value changes. The changed text is received from the native iOS code.
In the example, we have a module ‘GetData’ and a method ‘getThing()’ that returns a promise.
Create two files ‘GetData.h’ and ‘GetData.m’ in ios directory. While '.h'
file declares the interface, '.m'
file implements the interface.
[js]
// GetData.h file
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface GetData : NSObject <RCTBridgeModule>
@end
[/js]
‘GetData’ is the module that is available in ‘NativeModules’ of react native. RCTBridgeModule is the protocol which is used to register module ‘GetData’ for bridging.
[js]
// GetData.m file
#import "GetData.h"
@implementation GetData
RCT_EXPORT_MODULE();
RCT_REMAP_METHOD(getThing,
resolver: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject)
{
NSString *thingToReturn = @"Hello World!";
resolve(thingToReturn);
}
@end
[/js]
In the above code, a method getThing()
is defined which returns a promise.
– NSString *thingToReturn = @”Hello World!”;
This line of code declares a string ‘thingToReturn’ which contains the value “Hello World!”.
– resolve(thingToReturn);
This line returns a resolver in JS code.
Furthermore, to use ‘GetData’ native module from JavaScript, you require NativeModules from ‘react-native’ and from NativeModules we get native module ‘GetData’ and use in the Js like ‘NativeModules.GetData’.
[js]
import {NativeModules} from ‘react-native’
[/js]
[js]
export default class IOS_ReactNative_communication extends Component {
constructor(props) {
super(props);
this.state = {
text: ‘Bye Bye World’
}
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>{this.state.text}</Text>
<TouchableOpacity>
<View style={styles.button}>
<Text style={styles.buttonText}
onPress={() => { this.buttonClick()}}>Change Text.</Text>
</View>
</TouchableOpacity>
</View>
);
}
}
[/js]
[js]
buttonClick = async () => {
let newString = await NativeModules.GetData.getThing();
this.setState({text: newString})
}
[/js]
As soon as the button clicks, buttonClick function is called and from that buttonClick function we can call the native method ‘getThing()’ similar to ‘NativeModules.GetData.getThing()’ and as this is a promise we can use ‘async’ and ‘await’.
Output :
After Button Clicking
Example 2
This example explains a callback in iOS native that can be used in JS.
It contains two files with extensions .h
and .m
. The content of the .h
file remains same. In .m
file we declare a method replaceString
which is used to replace the ‘First’ text with ‘Second’ in a string ‘input’ received from JS.
[js]
// GetData.m file
#import "GetData.h"
@implementation GetData
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(replaceString:(NSString *)input callback:(RCTResponseSenderBlock)callback)
{
callback(@[[input stringByReplacingOccurrencesOfString:@"First" withString:@"Second"]]);
}
@end
[/js]
[js]
//index.io.js
import React, { Component } from ‘react’;
import {
AppRegistry,
StyleSheet,
Text,
View,
NativeModules,
TouchableOpacity
} from ‘react-native’;
export default class IOS_ReactNative_communication extends Component {
constructor(props) {
super(props);
this.state = {
text: ‘First Example’
}
}
buttonClick = () => {
NativeModules.GetData.replaceString(this.state.text, (text) => {
this.setState({text});
});
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>{this.state.text}</Text>
<TouchableOpacity>
<View style={styles.button}>
<Text style={styles.buttonText}
onPress={() => { this.buttonClick()}}>Change Text.</Text>
</View>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: ‘center’,
alignItems: ‘center’,
backgroundColor: ‘#F5FCFF’,
},
welcome: {
fontSize: 20,
textAlign: ‘center’,
margin: 10,
},
instructions: {
textAlign: ‘center’,
color: ‘#333333’,
marginBottom: 5,
},
button: {
backgroundColor: ‘#ff8000’,
borderRadius: 4,
padding:10
},
buttonText : {
color : ‘#fff’,
textAlign:’center’,
fontWeight: ‘bold’
},
});
AppRegistry.registerComponent(‘IOS_ReactNative_communication’, () => IOS_ReactNative_communication);
[/js]
The NativeModules contains the ‘GetData’ module which has ‘replaceString’ method.
Output :
After clicking on button
It is also possible to write the same iOS native code in swift if you are familiar with swift.
Bridging can also be achieved in Android and react-native in the same way as we have achieved in this blog.
You can find the code snippets of above examples here Hope these snippets and information provided in the blog is helpful.
Thanks for the article, could you tell me where I need to place these .h and .m files and how IOS will going to take these files and link with app?
Great article, simple and to the point; I’d done Native coding with React Native 6 months ago and was pulled from the project; when I returned it didn’t work – your example helped me get up and running again 🙂