Technical Articles
UI5 QR Scanner
Introduction
It’s a question that has been asked often by customers and as well in the SAP community:
How can we implement a QR scanner into a Fiori app? Do we need to use a cordova plugin for this? Do we need to make a hybrid app?
The answer is simple, this can be done in Fiori without the need of a cordova plugin nor making it hybrid.
In the past it was not possible to use the camera directly from a web application on all mobile devices. For Android phones it is already possible for a while. iOS devices on the other hand had to wait until iOS 11. Even though this was already released a few years ago, I’m not sure everyone is aware of the fact that this means iOS supports WebRTC. Which again means, you can use the camera of your phone directly in Safari :). No struggling with Cordova plugins and hybrid apps 🙂
More information about WebRTC support for iOS:
https://flashphoner.com/ios-safari-11-now-supports-webrtc/
Solution
I created a demo application of a QR scanner inside a fiori app. Here you have a video on how it works.
If my trial account is still active you can try it yourself here: https://2d32c6fatrial-workspaces-ws-w6zlm-app2.eu10.trial.applicationstudio.cloud.sap/bewlScannerApp/index.html
The demo app allows you to:
- Select any available camera
- Mirror the camera or not
- Scan QR codes
How it works
First of all, I did not all implement this myself ? I used a great and easy to use library for this: Instascan. More information about the library can be found here: https://github.com/schmich/instascan .
The problem of this library is that it’s not well maintained. When you look at the pull requests, you’ll notice many un-merged pull requests. Some of them contain fixes for iOS… Therefore, I started from this Pull Request: https://github.com/CoyoteLeo/instascan
Next to Instascan, I also used a WebRTC adapter library: https://github.com/webrtc/adapter
This library will handle differences of WebRTC implementation in different browsers. You have to keep this library up to date in case a browser does an update of the WebRTC implementation.
I created a UI5 Library with a UI5 control for the QR Scanner. To do this, I’ve added the istanscan and webrtc library into my UI5 Library so I’m able to use them in my UI5 Control.
The renderer of the UI5 QR Scanner control is pretty basic, it contains a toolbar with:
- A dropdown to switch cameras in case there is more than one (front and back on phone)
- A button to enable or disable mirroring
(The toolbar is created with UI5 controls in the init function of the implementation code.)
After the toolbar comes the video html tag for showing the camera.
The renderer function is using the new control api:
var QRScannerRenderer = {
apiVersion: 2,
render: function (oRm, oControl) {
oRm.openStart("div", oControl).style("width", "100%").style("height", "100%").openEnd();
oRm.renderControl(oControl.getAggregation("_toolbar"));
oRm.openStart("video", oControl).style("width", "100%").style("height", "100%").openEnd().close("video");
oRm.close("div");
}
};
In the “onAfterRendering” function of the control I initialize the camera using the Instascan library. This is also the place where I bind it to the “video” tag in the renderer function. I’m using “lastElementChild” on “getDomRef” because I wrapped it together with the toolbar in a div and it’s the last element in that div.
Next, the code will assign an eventhandler to handle scanned objects. It will also get the available cameras to add it to the dropdownlist.
Last but not least, it will start the scanner using the first one it can find.
onAfterRendering: function (evt) {
this.scanner = new Instascan.Scanner({
video: this.getDomRef().lastElementChild,
mirror: false
});
this.scanner.addListener('scan', function(qrcode){
this.onQRCodeScanned(qrcode);
}.bind(this));
Instascan.Camera.getCameras().then(function (cameras) {
this.cameras = cameras;
cameras.forEach(function (camera, key) {
this._cameraSelection.addItem(new Item({ key: key, text: camera.name }));
}.bind(this))
if (cameras.length > 0) {
this.scanner.start(cameras[0]);
} else {
console.error('No cameras found.');
}
}.bind(this)).catch(function (e) {
console.error(e);
});
},
In the eventhandler to handle scanned object I update the property “value” to update bindings and fire the event so it can be catched in views/controllers:
onQRCodeScanned: function (qrcode) {
this.setProperty("value", qrcode, true);
this.fireQRCodeScanned({
value: qrcode
});
},
When another camera is selected in the dropdown it will start the scanner on the newly selected camera. For this I have implemented the following eventhandler in the control:
onChangeCamera: function (event) {
this.scanner.start(this.cameras[event.getParameter("selectedItem").getKey()]);
},
Depending on which camera you are using, it might be required to enable or disable mirroring. In case of using the back camera of your phone it should be fine without mirroring. In case you use the front camera, you might consider using mirroring. For this I’ve added a switch to the toolbar that will change the mirroring property:
onChangeMirror: function (event) {
this.scanner.mirror = event.getParameter("state");
}
The full project is available on GitHub: https://github.com/lemaiwo/UI5QRScannerApp
It is one mta that contains the UI5 Library with the UI5 QR Scanner control and a UI5 Demo app.
The UI5 Library with the UI5 QR SCanner:
https://github.com/lemaiwo/UI5QRScannerApp/tree/master/ScannerAppLibrary
The UI5 demo app as an example how to use this UI5 QR Scanner control in a UI5 App:
https://github.com/lemaiwo/UI5QRScannerApp/tree/master/ScannerApp
You can clone the full project into the Business Application Studio (BAS) and run it direclty in BAS or deploy it to CF and run it using the Central AppRouter. To support my library in BAS and the Central App I added the following in the component.js:
( I know this is not the best way to do this, but adding the library in the manifest in combination with resourceroots could make it work in BAS or with the central AppRouter. Both approuters (BAS vs Central) generate a different url. For that reason, I did it the “ugly” way to make it work in BAS and the central AppRouter by adding this check in the component.js. If running in BAS, load the library from the provided path in BAS, otherwise use the path that’s provided by the Cetnral AppRouter. Any feedback to improve this is welcome!)
if(location.host.indexOf("studio")>-1){
//to make it work in app studio
sap.ui.getCore().loadLibrary("be.wl.ScannerAppLibrary", "/bewlScannerAppLibrary/be/wl/ScannerAppLibrary");
}else{
//to make it work in central approuter
sap.ui.getCore().loadLibrary("be.wl.ScannerAppLibrary", "/bewlscannerapp.bewlScannerAppLibrary/resources/be/wl/ScannerAppLibrary");
}
For running in BAS you also need to change your config to the following:
{
"name": "Run ScannerAppProject-ScannerApp",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/ScannerAppProject/node_modules/@sap/html5-repo-mock/index.js",
"args": [
"--standalone",
"/bewlScannerApp/index.html",
"--ui5",
"{\"version\":\".\"}"
],
"cwd": "${workspaceFolder}/ScannerAppProject/ScannerApp",
"env": {
"PORT": "6004",
"MOCK_LOOKUP_DIRS": "[\"webapp\",\"src\"]",
"run.config": "{\"handlerId\":\"ui5_run_config_handler_id\",\"runnableId\":\"/home/user/projects/ScannerAppProject/ScannerApp\"}"
},
"envFile": "${workspaceFolder}/ScannerAppProject/ScannerApp/.env1",
"preLaunchTask": "Build ScannerAppProject"
}
Watch carefully for “MOCK_LOOKUP_DIRS”. This has been extended to look for the “src” folder of the library as well:
On trial accounts the HTML5 app repo is not yet visible ( at least in my account… ) but you can find the list of deployed apps to the Central AppRouter by using the following command:
cf html5-list -u -d
Hi Lemaire,
very good article.
By chance I had to deal with the topic this week.
We use an app to read QR Codes, using sap.ndc.BarcodeScanner.
The app is executed in the SAP Fiori Client. Unfortunately we had problems with the Fiori client during development and I have implemented a fallback if the app is not running in a Cordova environment.
I used the library jsQR.
But at that time there were always problems with iOS and using the camera from the browser and we stayed with the Fiori Client.
But this week I tested it again and it worked very well with iOS version 14 in the Safari browser.
Alternative libraries that are also well maintained are JsQR scanner and html5-qrcode. But I haven't tried them myself yet.
Regards,
Marian
When I deploy this app onto my CF subaccount (BTW, Im using the productive account, not the trail account), I get the error.
Hi Wouter Lemaire,
I am very new to the Fiori & Ui5.
Can you please guide me step by step to develop the QR code scanner.