Foreword
Part 1 of this blog post introduced a barcode scanner hybrid Kapsel app, and covered the details of creating an OData service for it. This part covers the steps needed to create the barcode scanner hybrid Kapsel app from scratch.
The purpose of the example app is to demonstrate that it is possible to couple modern SAP UX technologies to an old 4.7 SAP application server, using an SAP NetWeaver 7.4 gateway system.
Using the camera of a mobile Android device, users can scan a bar code and show the corresponding material from a 4.7 SAP application server. The material scanning app will be launched from a Fiori Launchpad tile. The Fiori Launchpad will be added to the Android home screen as a full screen web app.
The presented app is not a reference. Its purpose is to demonstrate key solutions involved in providing the required functionality.
This blog was made possible by Bertelsmann Media Sp. z o.o. Oddział arvato Polska. Special thanks to Piotr Jakubowski, Andrzej Nowak and Krystian Brydzki for supporting the creation of this blog.
Overview
App development
- Material OData service (blog post part 1)
- Barcode scanner Kapsel hybrid app (blog post part 2)
Table of contents
Creating the scanner Kapsel hybrid app
Development environment
Install and configure your tool set according to the OpenSAP 'mobile2' course document 'openSAP_mobile2_Week_0_Unit_4_SYDE2_Guide.pdf' [
M2W0U4].
Creating a virtual host in the SAP HANA Cloud Connector
Create a virtual host in the HANA Cloud Connector for the gateway system EL6:
- Back-end Type: ABAP System
- Protocol: HTTP (or HTTPS)
- Virtual host and port: 'sapecbox:8060'
- Accessible resources, path and all sub-paths:
- /api_mgmt_proxy
- /plugins/pluginrepository
- /sap/bc/adt
- /sap/bc/bsp
- /sap/bc/ui5_ui5
- /sap/opu/odata
- /sap/public
- Perform the internal host availability check
Creating an HCP destination for the OData service
Create a HANA Cloud Platform (HCP) destination for the ZTUT00_MAT_00_SRV OData service on 'sapecbox:8060':
- Open your HANA Cloud Platform (HCP) trial account
- Open 'Connectivity / Cloud Connectors' and make sure it is 'Connected'
- Open 'Destinations', create a new destination:
- Name: 'sapecbox-8060'
- Type: HTTP
- URL: http://sapecbox:8060/
- Proxy Type: OnPremise
- Authentication: BasicAuthentication
- User: <your_test_user_on_EC6> e.g. FIORDEMO
- Password: <password_of_FIORDEMO>
- Additional Properties:
- sap-client: <client_on_EC6> e.g. 505
- WebIDEEnabled: true
- WebIDESystem: EL6
- WebIDEUsage: odata_abap,odata_gen,ui5_execute_abap,dev_abap,bsp_execute_abap,plugin_repository
- Perform the destination availability check
- Occasionally it may be necessary to perform the test quite a few times until it becomes successful (green)
Displaying the material list and material details
Creating the barcode scanner hybrid Kapsel app
Create a hybrid Kapsel app to display materials using the OData service ZTUT00_MAT_00_SRV:
- Open your HANA Cloud Platform (HCP) trial account
- Open the SAP Web IDE service
- Go to 'Preferences / Plugins', enable the 'Hybrid App Toolkit' (HAT) plugin
- Test the connection and make sure the Web IDE can connect to your local HAT installation
- Create a new project from the template 'SAPUI5 Master Detail Kapsel Application'
- Project name: <your>.<fqdn>.tut00mat00, e.g. 'pl.arvato.tut00mat00'
- Sources: 'Service Catalog', choose the destination added above, e.g. 'sapecbox-8060', choose the service 'ZTUT00_MAT_00_SRV', then 'Next'
- Project Namespace: <your>.<fqdn>.tut00mat00, e.g. 'pl.arvato.tut00mat00'
- Master Section
- Title: Material List
- OData Collection: MaterialSet
- Search Placeholder: MatNr
- Search Tooltip: Material number
- Search Field: MatNr
- Main Data Fields:
- Item Title: MatNr
- Numeric Attribute: GrossWt
- Units Attribute: UnitOfWt
- Detail Section:
- Title: Material Details
- Additional Attribute 1: MatlDesc
- Click 'Next', 'Finish'
- Fix a bug in the new project that comes from the template:
- Edit 'view/Master.controller.js', replace all 'sProductPath' with 'sEntityPath'. In onDetailChanged(), the oData parameter has no 'sProductPath' property. The property is 'sEntityPath'.
Running the barcode scanner app in Chrome
- Highlight the new project 'pl.arvato.tut00mat00'
- Click the green 'Run' icon on the toolbar
- The app is started in a new Chrome tab
- The app displays a list of materials. Choosing a material brings up its details.
Initializing the local git repository of the project
- Go to your HCP account / Repositories / Git Repositories
- Create a new repository
- Repository Name: tut00mat00
- Description: pl.arvato.tut00mat00 barcode scanned hybrid Kapsel app
- OK
- Click on the new repository and copy its 'Git Repository URL' to the clipboard
- Go to the Web IDE
- Right click the project 'pl.arvato.tut00mat00', choose Git / Initialize Local Repository
- Paste the copied URL to the URL field, optionally adding your user name, e.g. 'https://username@...'
- OK
- Open the 'Git Pane' from the right side of the Web IDE
- Perform a 'Pull'
- Click 'Stage All'
- Fill in 'Commit Description': 'Initial import'
- Click 'Commit and Push / Remote Branch'
- Choose 'origin/master', OK
Deploying the barcode scanner app on an Android device
- Set the Cordova Android version for your local HAT to at least '5.1.0':
- Edit your local '<HAT_installation>/config.json', set "platforms"."android"."version" to "5.1.0"
- Connect your Android device (or start an emulator)
- Make sure the Android debugger sees the device:
- command_line$ adb devices
- In Web IDE, right click the project, choose 'Project Settings / Device Configuration'
- Application
- App name: 'My First Material Scanner App'
- App id: 'pl.arvato.mat00tut00'
- Description: 'Scan a barcode to display material details'
- Build Options
- 'Debug Mode' - to allow app debugging from Chrome
- Platforms
- Check 'Android'
- Plugins
- Kapsel
- The app, when deployed on a mobile device, will use HTTPS to access the OData service. In order to handle user authentication, use the Kapsel 'Logon Manager' plugin:
- Check 'Logon Manager'
- Set the 'HCPms Host' to 'noserver.ondemand.com', since the HANA Cloud Platform mobile services will not be used here
- Edit 'dev/devlogon.js' 'doLogonInit' function, change 'if (window.sap_webide_companion) {' to
if (window.sap_webide_companion || context.serverHost === "noserver.ondemand.com") {
- Custom
- Instead of the relatively old Kapsel 'Barcode Scanner' plugin, use the custom 'phonegap-plugin-barcodescanner' plugin:
- Add or Remove / 'phonegap-plugin-barcodescanner', '+'
- OK
- Preferences
- Android
- Set 'URI Scheme' to 'pl.arvato.tut00mat00', for Android/Chrome intent navigation 'pl.arvato.tut00mat00:///'
- Save the device configuration
- Set the externalURL of the OData service:
- Edit '.project.json', add after "hcpmsServer":
"externalURL": "http[s]://<URL_of_gateway>:<port>/sap/opu/odata/sap/ZTUT00_MAT_00_SRV",
- Right click the project, Run / Run On / Android Device
- Wait for the build to complete
- The app will start on your mobile device, and will probably ask for a user name and password as part of the HTTP 'basic authentication'
- In case you want to use client certificate authentication [CLCRT]:
- Edit 'hybrid/config.xml', add into the '<widget>' section:
<platform name="android">
<preference name="SAPKapselHandleHttpRequests" value="true" />
</platform>
- Note: you will have to reapply this change after each deployment from the Web IDE
- cd hybrid
- cordova run android --device
- The app should display the same list of materials and material details as before, when it was run in Chrome
Adding the scanner button
Currently the user can display material details by selecting an item on the 'Material List' screen, but can not scan a barcode. Add a button to show material details corresponding to a scanned barcode:
- Open the 'Master.view.xml' of the app in the text editor of the Web IDE
- Add the scanner button 'sap.ndc.BarcodeScannerButton' [BCSCR] to the top right content area, into '<Bar id="searchBar">':
<contentRight>
<ndc:BarcodeScannerButton scanSuccess="handleBarcodeScannerSuccess" />
</contentRight>
- Add namespace 'xmlns:ndc="sap.ndc"' to the view: '<mvc:View ... xmlns:ndc="sap.ndc" ...>'
- Add a new event handler method 'handleBarcodeScannerSuccess' to 'Master.controller.js':
handleBarcodeScannerSuccess: function(event) {
//console.log("handleBarcodeScannerSuccess " + JSON.stringify(event.mParameters));
if(!event.mParameters.cancelled){
// Make sure you update the path below to your formatter function:
var sEntityPath = pl.arvato.tut00mat00.util.Formatter.encodeMaterialSetMatNr(event.mParameters.text);
var bReplace = jQuery.device.is.phone ? false : true;
this.showDetailForPath(sEntityPath, bReplace);
}
}
- Use the router's hash navigation [HNAV] to display the detail page of the scanned material. Add to 'Master.controller.js':
showDetailForPath: function(path, bReplace) {
this.getRouter().navTo("detail", {
from: "master",
entity: path,
tab: this.sTab
}, bReplace);
},
- Add a formatter method to 'util/Formatter.js':
encodeMaterialSetMatNr: function(matNr){
var newMatNr = encodeURIComponent(matNr).replace(/'/g, "%27");
var sEntityPath = "MaterialSet('" + newMatNr + "')";
return sEntityPath;
}
- Test the application in Chrome (in case you have a camera)
- Scan a valid bar code: the app will navigate to the corresponding detail view
- Scan an invalid bar code: 'Not Found / The requested resource was not found' is shown
Importing the Kapsel project to Android Studio
Certain device specific development can be performed
much more easily in Android Studio than in the Web IDE, because of the shorter time it takes to test changes. In this case, 'git' is needed to keep track of, and synchronize changes introduced in Android studio or the Web IDE. Unfortunately this development methodology is currently not well supported. In particular, the local git working copy established in '<tut00mat00_project>/hybrid/platforms/android/assets/www' is destroyed after each redeployment from the Web IDE.
- Go to the Web IDE
- Edit 'dev/devapp.js' 'onDeviceReady' function, change '$.getJSON(".project.json", function(data) {' to
$.getJSON((window.cordova ? "project.json" : ".project.json"), function(data) {
- Edit '.gitignore' and add:
cordova-js-src
cordova.js
cordova_plugins.js
plugins
resources
smp
- Commit and push all your changes to git
- Use your favorite git client to clone the git repository to a temporary location on your desktop computer
- Create and check out a new branch 'platforms/android' for the local copy
- Push the new branch back to the HCP 'origin'
- Copy '.git' of the temporary working copy to '<tut00mat00_project>/hybrid/platforms/android/assets/www'
- Note: you will have to reapply this change after each deployment from the Web IDE
- Stage and commit changes to 'index.html' and 'project.json'
- Discard local changes to all other files if any
- For faster 'gradle' builds, create the file '<tut00mat00_project>/hybrid/platforms/android/gradle.properties' in the locally deployed project, with this content: 'org.gradle.jvmargs=-Xmx2048M'
- Note: you will have to reapply this change after each deployment from the Web IDE
- Start Android Studio
- Import Project / '<tut00mat00_project>/hybrid/platforms/android'
- Note: you will have to reapply this change after each deployment from the Web IDE
- Keep the 'gradle' version (recommended) - or -
- Allow Android Studio to update gradle to a newer version, click 'Fix Gradle wrapper and re-import project' if it appears
- Wait for the initial Gradle build to complete
- Choose 'Run' menu / Run 'android' / Choose an Android device
- Test the scanner button on valid, and invalid bar codes
Launching the scanner hybrid Kapsel app from a Launchpad tile
- Open the Fiori Launchpad Designer
- Create a new tile in the catalog of your choice with semantic navigation, to semantic object 'Material', action 'tut00mat00'
- Create a target mapping for 'Material' 'tut00mat00':
- Application Type: 'URL'
- URL: 'pl.arvato.tut00mat00:///'
- 'Device Types': uncheck 'Desktop' (optional)
- Open the Fiori Launchpad on the mobile device in Chrome
- Launch the app by touching the new tile
Handling Android lifecycle events
In case your Android test device has sufficient amount of RAM, the scanner app has most likely worked correctly so far. However, in case of RAM shortage, Android may kill the app while performing the scanning activity, and restart the app afterwards. This is normal behavior [
ALG], that will certainly happen occasionally. In order to test the behavior of the scanner app under low memory conditions, enable 'Settings / Developer options / Don't keep activities'. This forces the app to be destroyed before, and recreated after each time it is put into the background. This means when performing a scanning or simply returning to the Home screen.
- Enable 'Settings/Developer options/Don't keep activities'
- Run the app
- Navigate to the details of a material
- Hit the home (circle) button
- Return to the app: note that the app no longer shows the previous material details. It has been restarted.
- Run the app again and perform a scanning. The app shows its initial state, instead of the details of the scanned item.
Returning to the last material detail screen when the app is resumed
Events 'pause' and 'resume' can be used to react to Android lifecycle events. The 'resume' event handler has to be set as early as possible, so before the component's init() is called.
- Store the material whose details are being shown on 'pause':
- Edit index.html, add after '<link rel="stylesheet" type="text/css" href="css/style.css">':
<script>
var APP_STORAGE_KEY = "pl.arvato.tut00mat00.appState";
var appState = { hash: null };
</script>
- Edit Component.js 'init:', add after 'this.setModel(i18nModel, "i18n");':
// Bind lifecycle events
this.bindPauseEvent();
- Edit new methods to the component:
bindPauseEvent: function(){
var that = this;
document.addEventListener("pause", function() {
that.handlePause.apply(that, arguments);
}, false);
},
handlePause: function(event){
// Save hash so we can return to it on resume
appState.hash = sap.ui.core.routing.HashChanger.getInstance().getHash() || "";
window.localStorage.setItem(APP_STORAGE_KEY, JSON.stringify(appState));
},
- Retrieve and show the last material upon 'resume':
- Edit index.html:
- Add after 'jQuery.sap.require("pl.arvato.tut00mat00.dev.devapp");':
jQuery.sap.require("pl.arvato.tut00mat00.util.earlyResume");
- Add after 'window.onload = function() {':
pl.arvato.tut00mat00.util.earlyResume.bindEvent();
- Create 'util/earlyResume.js':
jQuery.sap.declare("pl.arvato.tut00mat00.util.earlyResume");
pl.arvato.tut00mat00.util.earlyResume = {
bindEvent: function(){
document.addEventListener("resume", function() {
pl.arvato.tut00mat00.util.earlyResume.handleEarlyResume.apply(null, arguments);
}, false);
},
handleEarlyResume: function(){
if(appState.hash === null){ // No previous state, restore from localStorage
var appStateJSON = window.localStorage.getItem(APP_STORAGE_KEY);
if(appStateJSON && appStateJSON !== "null"){
appState = JSON.parse(appStateJSON);
if(appState.hash){
// There is no sap.ui.core.routing.HashChanger yet. Defer the below till later, when there is:
appState.controllerParentInitDeferred = jQuery.Deferred();
jQuery.when(appState.controllerParentInitDeferred).then(function() {
delete appState.controllerParentInitDeferred;
sap.ui.core.routing.HashChanger.getInstance().setHash(appState.hash);
});
}
}
}
}
};
- Edit 'Component.js', add to 'init' after 'sap.ui.core.UIComponent.prototype.init.apply(this, arguments);':
// Resolve deferred object of resume
if(typeof appState !== 'undefined' && appState.controllerParentInitDeferred){ appState.controllerParentInitDeferred.resolve(); }
- Run the app and test:
- Choose a material to display its details
- Go to the Home screen (circle button)
- Return to the app
- The app is restarted and returns to the last material displayed
Displaying the scanned material when the app is resumed
Handle the 'resume' event that carries the result of the scanner plugin:
- Edit 'Component.js', add to the top:
jQuery.sap.require("pl.arvato.tut00mat00.util.Formatter");
- Edit 'util/earlyResume.js'
- Add after 'if(appState.hash === null){':
// Handle pending plugin results if any
if (event && event.pendingResult && event.pendingResult.pluginServiceName &&
event.pendingResult.pluginServiceName === "BarcodeScanner" &&
event.pendingResult.pluginStatus === "OK" && !event.pendingResult.result.cancelled) {
pl.arvato.tut00mat00.util.earlyResume.handleBarcodeScannerPendingResult(event);
return;
}
- Add a new method:
handleBarcodeScannerPendingResult: function(event){
//console.log("resume registering scanner deferred: " + JSON.stringify(event));
appState.controllerParentInitDeferred = jQuery.Deferred();
jQuery.when(appState.controllerParentInitDeferred).then(function() {
delete appState.controllerParentInitDeferred;
// Set URL hash e.g. #/MaterialSet('MatNr%201') and start with that
var sEntityPath = pl.arvato.tut00mat00.util.Formatter.encodeMaterialSetMatNr(event.pendingResult.result.text);
//console.log("resume early deferred: about to set hash to " + sEntityPath);
sap.ui.core.routing.HashChanger.getInstance().setHash(sEntityPath);
});
}
- The current version (6.0.1) of the barcode scanner plugin 'phonegap-plugin-barcodescanner' does not handle application resume, hence its result is lost. A simple patch fixes this:
- Edit (in e.g. Android Studio) 'android / java / com.phonegap.plugins.barcodescanner / BarcodeScanner.java' and override 'onRestoreStateForActivityResult' of the BarcodeScanner class:
@Override
public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {
this.callbackContext = callbackContext;
}
- Add the corresponding import:
import android.os.Bundle;
- Run and test the app
- After scanning, the details of the scanned material are shown
- Disable 'Settings / Developer options / Don't keep activities'
Do not forget to commit your changes to the git repository.
Adding the Fiori Launchpad to the Android Home screen as a web app
Make the Fiori Launchpad open like a native Android app [
WAPP], by adding it to the Home screen:
- Create the Android web app 'manifest.json' file on your local machine:
{
"name": "Tutorial Launchpad",
"description" : "Tutorial Launchpad - Start Here",
"start_url": "/sap/bc/ui5_ui5/sap/ztut00ushell00/shells/abap/FioriLaunchpad.html",
"display": "standalone",
"orientation": "any",
"background_color": "white"
}
- Start the ABAP Development Workbench (SE80)
- Make a copy of the BSP project '/UI2/USHELL' to 'ZTUT00USHELL00'
- Edit the new 'shells/abap/FioriLaunchpad.html' and add before '<!-- iPhone splash screens -->':
<!-- Android web app -->
<!-- https://developer.chrome.com/multidevice/android/installtohomescreen -->
<link rel="manifest" href="https://blogs.sap.com/sap/bc/bsp/sap/ztut00ushell00/manifest.json" />
- Import 'manifest.json' as a new MIME object 'manifest.json':
- Mime Type: 'application/manifest+json'
- Activate the BSP project
- Create a new ICF node
- SICF / /default_host/sap/bc/ui5_ui5/sap/ / New Sub-Element
- Name of Service: 'ztut00ushell00'
- Save the new service and go back to the 'Maintain service' view
- Activate the 'ztut00ushell00' service
- Test the new launchpad
- Navigate to 'https://<yourgw>:<port>/sap/bc/ui5_ui5/sap/ztut00ushell00/shells/abap/FioriLaunchpad.html'
- Test the manifest file by opening 'https://<yourgw>:<port>/sap/bc/bsp/sap/ztut00ushell00/manifest.json'
- Add the new launchpad to the Android Home screen
- Navigate to the new launchpad in Chrome on the mobile device
- Choose 'Add to Home screen'
- Launch the app from the Android Home screen
- The app is launched in full screen mode
This concludes the tutorial.
Further reading
[M2W0U4]
openSAP mobile2 / Setting Up Your Development Environment
[CLCRT]
Getting Started with Kapsel - Part 8 -- AuthProxy
[BCSCR]
SAPUI5 SDK - Demo Kit / sap.ndc.BarcodeScannerButton
[HNAV]
Navigation - UI Development Toolkit for HTML5 (SAPUI5) - SAP Library
[ALG]
Android Platform Guide - Apache Cordova - Lifecycle Guide
[WAPP]
Add web app to Homescreen - Google Chrome
Afterword to this blog post
Thank you for reading this blog post. I hope you found it useful.