Communicating To and From Native UI Components in React Native Android
If you have a basic knowledge of how ReactNative works. You might know there are two sides in ReactNative Application
- ReactJavaScript side
- Android/IOS Native side
Assume these two sides as two sides of a river. If you want to cross the river you need a bridge. I will explain how to build this bridge (Check this movie if you have time :) )
There are two ways in which we can communicate
- NativeModules (Native code which does not return any View)
- Native UI components ( Native code which returns a Native View which is rendered in React)
As there is good documentation on NativeModule. I will be explaining Native UI Components in more detail rather than NativeModules
ReactNative provides a wide variety of Components which can be used in javascript directly. But what if you want a use CustomUI Component. For Example: Custom Views, Canvas
I had a requirement of taking the Signature of the user in my Android App. There is no React Component for taking signature. So googled around and found react-native-signature-capture native ui component . This component captures the signature of the user and returns a Base64 encoded string.
As though, this component was serving the basic need(Taking signature). There was only one way communication(From Native to React Javascript) and there was no other way communication(ReactJavascript to Native UI Component).
There were two buttons “Save” and “Reset” which were added in the native code.
When the “Save” button is clicked. The Base64 equivalent of the signature image is returned from native code to react
This is how you can add a signature view in your react component.
Add the signature capture module in your react native application as per the readme of react-native-signature-capture
import SignatureCapture from ‘react-native-signature-capture’;<SignatureCapture
onSaveEvent={this._onSaveEvent}
/>
_onSaveEvent(result) {
result.encoded - for the base64 encoded png
result.pathName - for the file path name
}
This component is working fine. When a signature is saved, It was triggering the callback on _onSaveEvent(result). This is a perfect example of communication from Native to ReactNative using callback. This is done by sending event from native side like this
Communication from Android Native -> ReactNativeJS
Code for sending event to javascript from Native code
WritableMap event = Arguments.createMap();
event.putString("message", "MyMessage");
ReactContext reactContext = (ReactContext)getContext();
reactContext.getJSModule(RCTEventEmitter.class).
receiveEvent(getId(),"topChange",event);
Confused with receiveEvent() ?
actually it meant for sending event to javascript. You are telling react’s JS module to receive event named topChange.
Note that , you can also send extra data with event using event.put<DataType>() methods
My requirement was different
- “Save” and “Reset” Buttons should match app’s theme
- Saving Signature image should be triggered from JS code
Communication from ReactNativeJs -> Android Native
Sending events to native code is different compared to sending events from native code. As there is no proper doc on this topic. I have posted a question in stackOverflow for the same. Thanks to agent_hunt and kmagiera , for pointing me in right direction.
I have removed native buttons (“save” and “reset”) and added buttons in react component. Like this
When the “Save” button is clicked, saveImage() of native code should be triggered and result should be returned back.
For this sending events to native ui component, we have to use two methods of ViewManager class.
getCommandsMap()
receiveCommand()
This is the ViewManager class in Native Java code
public class RSSignatureCaptureViewManager extends ViewGroupManager<RSSignatureCaptureMainView> {
private Activity mCurrentActivity;
public static final int COMMAND_SAVE_IMAGE = 1;
public static final int COMMAND_RESET_IMAGE = 2;
public RSSignatureCaptureViewManager(Activity activity) {
mCurrentActivity = activity;
}
@Override
public String getName() {
return "RSSignatureView";
} @Override
public RSSignatureCaptureMainView createViewInstance(ThemedReactContext context) {
Log.d("React"," View manager createViewInstance:");
return new RSSignatureCaptureMainView(context, mCurrentActivity);
}
@Override
public Map<String,Integer> getCommandsMap() {
Log.d("React"," View manager getCommandsMap:");
return MapBuilder.of(
"saveImage",
COMMAND_SAVE_IMAGE,
"resetImage",
COMMAND_RESET_IMAGE);
}
@Override
public void receiveCommand(
RSSignatureCaptureMainView view,
int commandType,
@Nullable ReadableArray args) {
Assertions.assertNotNull(view);
Assertions.assertNotNull(args);
switch (commandType) {
case COMMAND_SAVE_IMAGE: {
view.saveImage();
return;
}
case COMMAND_RESET_IMAGE: {
view.reset();
return;
}
default:
throw new IllegalArgumentException(String.format(
"Unsupported command %d received by %s.",
commandType,
getClass().getSimpleName()));
}
}
}
We have added two commands in ViewManager “saveImage” and “resetImage” in getCommandsMap()
So we can dispatch these commands from React JS code using UIManager.dispatchViewManagerCommand() method. This method is used in ViewPagerAndroid component of ReactNative.
class SignatureExample extends Component {
render() {
return (
<SignatureCapture
ref="sign"
onSaveEvent={this._onSaveEvent}/>
<View style={{ flex: 1, flexDirection: "row" }}>
<TouchableHighlight style={styles.buttonStyle}
onPress={() => { this.saveSign() } } >
<Text>Save</Text>
</TouchableHighlight>
<TouchableHighlight style={styles.buttonStyle}
onPress={() => { this.resetSign() } } >
<Text>Reset</Text>
</TouchableHighlight>
</View>
</View>
);
}
saveSign() {
UIManager.dispatchViewManagerCommand(
React.findNodeHandle(this),
UIManager.RSSignatureView.Commands.saveImage,
[],
);
}
resetSign() {
UIManager.dispatchViewManagerCommand(
React.findNodeHandle(this),
UIManager.RSSignatureView.Commands.resetImage,
[],
);
}_onSaveEvent(result) {
result.encoded - for the base64 encoded png
result.pathName - for the file path name
}}
Here the Sample code of this project.
Libraries used: react-native-signature-capture