Introduction
React Native is a convenient tool for cross platform development, and though it has become more and more powerful through the updates, there are limits to it, for example its capability to interact with and using the native components. Bridging native code with Javascript is one of the most popular and effective ways to solve the problem. Best of both worlds!
Currently not all HMS Kits has official RN support yet, this article will walk you through how to create android native bridge to connect your RN app with HMS Kits, and Scan Kit will be used as an example here.
The tutorial is based on https://github.com/clementf-hw/rn_integration_demo/tree/4b2262aa2110041f80cb41ebd7caa1590a48528a, you can find more details about the sample project in this article: https://forums.developer.huawei.com...d=0201230857831870061&fid=0101187876626530001.
Prerequisites
Basic Android development
Basic React Native development
These areas have been covered immensely already on RN’s official site, this forum and other sources
HMS properly configured
You can also reference the above article for this matter
Major dependencies
RN Version: 0.62.2 (released on 9th April, 2020)
Gradle Version: 5.6.4
Gradle Plugin Version: 3.6.1
agcp: 1.2.1.301
This tutorial is broken into 3 parts:
Pt. 1: Create a simple native UI component as intro and warm up
Pt. 2: Bridging HMS Scan Kit into React Native
Pt. 3: Make Scan Kit into a stand alone React Native Module that you can import into other projects or even upload to npm.
Bridging HMS Scan Kit
Now we have some fundamental knowledge on how to bridge, let’s bridge something meaningful. We will bridge the Scan Kit Default View as a QR Code Scanner, and also learn how to communicate from Native side to React Native side.
First, we’ll have to configure the project following the guide to set Scan Kit up on the native side: https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/scan-preparation-4
Put agconnect-service.json in place
Add to allprojects > repositories in root level build.gradle
Add to buildscript > repositories
Add to buildscript > dependencies
Go to app/build.gradle and add this to header
Add this to dependencies
Add in proguard-rules.pro
Now do a gradle sync. Also you can try to build and run the app to see if everything’s ok even though we have not done any actual development yet.
Add these to AndroidManifest.xml
So the basic setup/configuration is done. Similar to the warm up, we will create a Module file first. Note that for the sake of variance and wider adaptability of the end product, this time we’ll make it a plain Native Module instead of Native UI Component.
We have seen how data flows from RN to native in the warm up (e.g. @reactProp of our button), There are also several ways for data to flow from native to RN. In Scan Kit, it utilizes startActivityForResult, therefore we need to implement its subsequent listeners.
There are couple small details we’ll need to add. First, React Native javascript side expects a Promise from the result.
We also need to add a request code to identify that this is our scan kit activity. 567 here is just an example, the value is of your own discretion
There will be several error/reject conditions, let’s identify and declare their code first
At this moment, the module should look like this
Now let’s implement the listener method
Let’s walk through what this does
When the listener receives an activity result, it checks if this is our request by checking the request code.
Afterwards, it checks if the promise object is null. We will cover the promise object later, but briefly speaking this is passed from RN to native, and we rely on it to send the data back to RN.
Then, if the result is a CANCELED situation, we tell RN that the scanner is canceled, for example closed by user, by calling promise.reject()
If the result indicates OK, we’ll get the data by calling getParcelableExtra()
Now we’ll see if the resulting data matches our data type and is not empty, and then we’ll call promise.resolve()
Otherwise we will resolve a general rejection message. Of course here you can expand and give a more detailed breakdown and resolution if you wish
This is a lot of checking and validation, but one can never be too safe, right?
Cool, now we have finished the listener, let’s work on the caller! This is the method we’ll be calling in RN side, indicated by the @reactMethod annotation.
[CODE @reactMethod
public void startScan(final Promise promise) {
} [/CODE]
Give it some content
[CODE @reactMethod
public void startScan(final Promise promise) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
// Store the promise to resolve/reject when picker returns data
mScannerPromise = promise;
try {
ScanUtil.startScan(currentActivity, REQUEST_CODE_SCAN, new HmsScanAnalyzerOptions.Creator().setHmsScanTypes(HmsScan.ALL_SCAN_TYPE).create());
} catch (Exception e) {
mScannerPromise.reject(E_FAILED_TO_SHOW_SCANNER, e);
mScannerPromise = null;
}
}[/CODE]
Let’s do a walk through again
First we get the current activity reference and check if it is valid
Then we take the input promise and assign it to mScannerPromise which we declared earlier, so we can refer and use it throughout the process
Now we call the Scan Kit! This part is same as a normal android implementation.
Of course we wrap it with a try-catch for safety purposes
At this point we have finished the Module, same as the warm up we’ll need to create a Package. This time it is a Native Module therefore we register it in createNativeModules() and also give createViewManagers() an empty list.
Same as before, we’ll add the package to our MainApplication.java, import the Package, and add it in the getPackages() function
All set! Let’s head back to RN side. This is our app from the warm up exercise(with a bit style change for the things we are going to add)
Let’s add a Button and set its onPress property as this.onScan() which we’ll implement after this
Reload and see the button
Similar to the one in the warm up, we can declare the Native Module using this simple way
Now we’ll implement onScan() which uses the async/await syntax for asynchronous coding
Important! Scan Kit requires CAMERA and READ_EXTERNAL_STORAGE permissions to function, make sure you have handled this beforehand. One of the recommended way to handle it is to use react-native-permissions library https://github.com/react-native-community/react-native-permissions. I will make another article regarding this topic, but for now you can refer to https://github.com/clementf-hw/rn_integration_demo if you are in need.
Now we click…TADA!
In this demo, this is what onScan() contains
Note: one minor modification is needed if you are basing on the branch of this demo project mentioned before
Now let’s try scan this
The actual data contained in the QR Code is
Which bring us to Auckland!
Now your HMS Scan Kit in React Native is up and running!
Pt. 2 of this tutorial is done, please feel free to ask questions. You can also check out the repo of the sample project on github: https://github.com/clementf-hw/rn_integration_demo, and raise issue if you have any question or any update.
In the 3rd and final part of this tutorial, we'll go through how to make this RN HMS Scan Kit Bridge a standalone, downloadable and importable React Native Module, which you can use in multiple projects instead of creating the Native Module one by one, and you can even upload it to NPM to share with other fellow developers.
React Native is a convenient tool for cross platform development, and though it has become more and more powerful through the updates, there are limits to it, for example its capability to interact with and using the native components. Bridging native code with Javascript is one of the most popular and effective ways to solve the problem. Best of both worlds!
Currently not all HMS Kits has official RN support yet, this article will walk you through how to create android native bridge to connect your RN app with HMS Kits, and Scan Kit will be used as an example here.
The tutorial is based on https://github.com/clementf-hw/rn_integration_demo/tree/4b2262aa2110041f80cb41ebd7caa1590a48528a, you can find more details about the sample project in this article: https://forums.developer.huawei.com...d=0201230857831870061&fid=0101187876626530001.
Prerequisites
Basic Android development
Basic React Native development
These areas have been covered immensely already on RN’s official site, this forum and other sources
HMS properly configured
You can also reference the above article for this matter
Major dependencies
RN Version: 0.62.2 (released on 9th April, 2020)
Gradle Version: 5.6.4
Gradle Plugin Version: 3.6.1
agcp: 1.2.1.301
This tutorial is broken into 3 parts:
Pt. 1: Create a simple native UI component as intro and warm up
Pt. 2: Bridging HMS Scan Kit into React Native
Pt. 3: Make Scan Kit into a stand alone React Native Module that you can import into other projects or even upload to npm.
Bridging HMS Scan Kit
Now we have some fundamental knowledge on how to bridge, let’s bridge something meaningful. We will bridge the Scan Kit Default View as a QR Code Scanner, and also learn how to communicate from Native side to React Native side.
First, we’ll have to configure the project following the guide to set Scan Kit up on the native side: https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/scan-preparation-4
Put agconnect-service.json in place
Add to allprojects > repositories in root level build.gradle
Code:
allprojects {
repositories {
google()
jcenter()
maven {url 'https://developer.huawei.com/repo/'}
}
}
Add to buildscript > repositories
Code:
buildscript {
repositories {
google()
jcenter()
maven {url 'https://developer.huawei.com/repo/'}
}
}
Add to buildscript > dependencies
Code:
buildscript{
dependencies {
classpath 'com.huawei.agconnect:agcp:1.2.1.301'
}
}
Go to app/build.gradle and add this to header
Code:
apply plugin: 'com.huawei.agconnect'
Add this to dependencies
Code:
dependencies {
implementation 'com.huawei.hms:scanplus:1.1.3.300'
}
Add in proguard-rules.pro
Code:
-ignorewarnings
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-keep class com.hianalytics.android.**{*;}
-keep class com.huawei.**{*;}
Now do a gradle sync. Also you can try to build and run the app to see if everything’s ok even though we have not done any actual development yet.
Add these to AndroidManifest.xml
Code:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<application
…
<activity android:name="com.huawei.hms.hmsscankit.ScanKitActivity" />
</application>
So the basic setup/configuration is done. Similar to the warm up, we will create a Module file first. Note that for the sake of variance and wider adaptability of the end product, this time we’ll make it a plain Native Module instead of Native UI Component.
Code:
package com.cfdemo.d001rn;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
public class ReactNativeHmsScanModule extends ReactContextBaseJavaModule {
private static final String REACT_CLASS = "ReactNativeHmsScan";
private static ReactApplicationContext reactContext;
public ReactNativeHmsScanModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
@NonNull
@Override
public String getName() {
return REACT_CLASS;
}
}
We have seen how data flows from RN to native in the warm up (e.g. @reactProp of our button), There are also several ways for data to flow from native to RN. In Scan Kit, it utilizes startActivityForResult, therefore we need to implement its subsequent listeners.
Code:
package com.cfdemo.d001rn;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
public class ReactNativeHmsScanModule extends ReactContextBaseJavaModule {
private static final String REACT_CLASS = "ReactNativeHmsScan";
private static ReactApplicationContext reactContext;
public ReactNativeHmsScanModule(ReactApplicationContext context) {
super(context);
reactContext = context;
reactContext.addActivityEventListener(mActivityEventListener);
}
@NonNull
@Override
public String getName() {
return REACT_CLASS;
}
private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
}
};
}
There are couple small details we’ll need to add. First, React Native javascript side expects a Promise from the result.
Code:
private Promise mScannerPromise;
We also need to add a request code to identify that this is our scan kit activity. 567 here is just an example, the value is of your own discretion
Code:
private static final int REQUEST_CODE_SCAN = 567
There will be several error/reject conditions, let’s identify and declare their code first
Code:
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
private static final String E_SCANNER_CANCELLED = "E_SCANNER_CANCELLED";
private static final String E_FAILED_TO_SHOW_SCANNER = "E_FAILED_TO_SHOW_SCANNER";
private static final String E_INVALID_CODE = "E_INVALID_CODE";
At this moment, the module should look like this
Code:
package com.cfdemo.d001rn;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
public class ReactNativeHmsScanModule extends ReactContextBaseJavaModule {
private static final String REACT_CLASS = "ReactNativeHmsScan";
private static ReactApplicationContext reactContext;
private Promise mScannerPromise;
private static final int REQUEST_CODE_SCAN = 567;
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
private static final String E_SCANNER_CANCELLED = "E_SCANNER_CANCELLED";
private static final String E_FAILED_TO_SHOW_SCANNER = "E_FAILED_TO_SHOW_SCANNER";
private static final String E_INVALID_CODE = "E_INVALID_CODE";
public ReactNativeHmsScanModule(ReactApplicationContext context) {
super(context);
reactContext = context;
reactContext.addActivityEventListener(mActivityEventListener);
}
@NonNull
@Override
public String getName() {
return REACT_CLASS;
}
private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
}
};
}
Now let’s implement the listener method
Code:
if (requestCode == REQUEST_CODE_SCAN) {
if (mScannerPromise != null) {
if (resultCode == Activity.RESULT_CANCELED) {
mScannerPromise.reject(E_SCANNER_CANCELLED, "Scanner was cancelled");
} else if (resultCode == Activity.RESULT_OK) {
Object obj = intent.getParcelableExtra(ScanUtil.RESULT);
if (obj instanceof HmsScan) {
if (!TextUtils.isEmpty(((HmsScan) obj).getOriginalValue())) {
mScannerPromise.resolve(((HmsScan) obj).getOriginalValue().toString());
} else {
mScannerPromise.reject(E_INVALID_CODE, "Invalid Code");
}
return;
}
}
}
}
Let’s walk through what this does
When the listener receives an activity result, it checks if this is our request by checking the request code.
Afterwards, it checks if the promise object is null. We will cover the promise object later, but briefly speaking this is passed from RN to native, and we rely on it to send the data back to RN.
Then, if the result is a CANCELED situation, we tell RN that the scanner is canceled, for example closed by user, by calling promise.reject()
If the result indicates OK, we’ll get the data by calling getParcelableExtra()
Now we’ll see if the resulting data matches our data type and is not empty, and then we’ll call promise.resolve()
Otherwise we will resolve a general rejection message. Of course here you can expand and give a more detailed breakdown and resolution if you wish
This is a lot of checking and validation, but one can never be too safe, right?
Cool, now we have finished the listener, let’s work on the caller! This is the method we’ll be calling in RN side, indicated by the @reactMethod annotation.
[CODE @reactMethod
public void startScan(final Promise promise) {
} [/CODE]
Give it some content
[CODE @reactMethod
public void startScan(final Promise promise) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
// Store the promise to resolve/reject when picker returns data
mScannerPromise = promise;
try {
ScanUtil.startScan(currentActivity, REQUEST_CODE_SCAN, new HmsScanAnalyzerOptions.Creator().setHmsScanTypes(HmsScan.ALL_SCAN_TYPE).create());
} catch (Exception e) {
mScannerPromise.reject(E_FAILED_TO_SHOW_SCANNER, e);
mScannerPromise = null;
}
}[/CODE]
Let’s do a walk through again
First we get the current activity reference and check if it is valid
Then we take the input promise and assign it to mScannerPromise which we declared earlier, so we can refer and use it throughout the process
Now we call the Scan Kit! This part is same as a normal android implementation.
Of course we wrap it with a try-catch for safety purposes
At this point we have finished the Module, same as the warm up we’ll need to create a Package. This time it is a Native Module therefore we register it in createNativeModules() and also give createViewManagers() an empty list.
Code:
package com.cfdemo.d001rn;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
public class ReactNativeHmsScanPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new ReactNativeHmsScanModule(reactContext));
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Same as before, we’ll add the package to our MainApplication.java, import the Package, and add it in the getPackages() function
Code:
import com.cfdemo.d001rn.ReactNativeWarmUpPackage;
import com.cfdemo.d001rn.ReactNativeHmsScanPackage;
public class MainApplication extends Application implements ReactApplication {
...
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new ReactNativeWarmUpPackage());
packages.add(new ReactNativeHmsScanPackage());
return packages;
}
All set! Let’s head back to RN side. This is our app from the warm up exercise(with a bit style change for the things we are going to add)
Let’s add a Button and set its onPress property as this.onScan() which we’ll implement after this
Code:
render() {
const { displayText, region } = this.state
return (
<View style={styles.container}>
<Text style={styles.textBox}>
{displayText}
</Text>
<RNWarmUpView
style={styles.nativeModule}
text={"Render in Javascript"}
/>
<Button
style={styles.button}
title={'Scan Button'}
onPress={() => this.onScan()}
/>
<MapView
style={styles.map}
region={region}
showCompass={true}
showsUserLocation={true}
showsMyLocationButton={true}
>
</MapView>
</View>
);
}
Reload and see the button
Similar to the one in the warm up, we can declare the Native Module using this simple way
Code:
const RNWarmUpView = requireNativeComponent('RCTWarmUpView')
const RNHMSScan = NativeModules.ReactNativeHmsScan
Now we’ll implement onScan() which uses the async/await syntax for asynchronous coding
Code:
async onScan() {
try {
const data = await RNHMSScan.startScan();
// handle your data here
} catch (e) {
console.log(e);
}
}
Important! Scan Kit requires CAMERA and READ_EXTERNAL_STORAGE permissions to function, make sure you have handled this beforehand. One of the recommended way to handle it is to use react-native-permissions library https://github.com/react-native-community/react-native-permissions. I will make another article regarding this topic, but for now you can refer to https://github.com/clementf-hw/rn_integration_demo if you are in need.
Now we click…TADA!
In this demo, this is what onScan() contains
Code:
async onScan() {
try {
const data = await RNHMSScan.startScan();
const qrcodeData = {
message: (JSON.parse(data)).message,
location: (JSON.parse(data)).location,
my_location: (JSON.parse(data)).my_location
}
this.handleData(qrcodeData)
} catch (e) {
console.log(e);
}
}
Note: one minor modification is needed if you are basing on the branch of this demo project mentioned before
Code:
onLocationReceived(locationData) {
const location = typeof locationData === "object" ? locationData : JSON.parse(locationData)
…
Now let’s try scan this
The actual data contained in the QR Code is
Code:
{"message": "Auckland", "location": {"lat": "-36.848461","lng": "174.763336"}}
Which bring us to Auckland!
Now your HMS Scan Kit in React Native is up and running!
Pt. 2 of this tutorial is done, please feel free to ask questions. You can also check out the repo of the sample project on github: https://github.com/clementf-hw/rn_integration_demo, and raise issue if you have any question or any update.
In the 3rd and final part of this tutorial, we'll go through how to make this RN HMS Scan Kit Bridge a standalone, downloadable and importable React Native Module, which you can use in multiple projects instead of creating the Native Module one by one, and you can even upload it to NPM to share with other fellow developers.