Skip to Content

One feature that I’ve never had the chance to deep-dive into is Offline OData. With the latest updates in Hybrid App Toolkit (HAT) on SAP Web IDE Full-Stack, the creation of offline apps has been made simpler. Read this great blog by Ludo Noens.

In this blog post, I’m going to show how simple it is to build an offline app from scratch using the HAT on Web IDE. I will be using Ludo’s blog as reference so I would recommend you to go through his post first if you haven’t done so.

I’m going to assume you have basic UI5 knowledge, activated the required cloud platform services, configured the sample cloud platform destination and activated the HAT feature as per Ludo’s blog. One change that I’ll make, though, is change the destination URL such that it points to the demo SAP mobile platform instead of directly to the ESPM sample service.

URL:  https://hcpms-<your account number>trial.hanatrial.ondemand.com instead of https://hcpms-<your account number>trial.hanatrial.ondemand.com//SampleServices/ESPM.svc

Feel free to use any destination if you already have a running OData service somewhere but ensure you replace the relevant property values in the examples below.

 

Ensure you have the correct Basic Auth credentials by accessing the URL directly and logging in with the username and password.

Let’s get started!

First, create an application in Web IDE Full-Stack using the SAP UI5 Application Template

Configuring a Hybrid Mobile Application

Next, enable the app as a Hybrid Mobile Application. Right click on your project > Mobile > Enable as Hybrid Mobile Project.


Note: If the ‘Mobile’ option is not on the list, check that you have activated the HAT feature in Web IDE.

Your project structure should now look like this. Additional details are available here.

 

Add the “sap.mobile” section in the manifest.json file to trigger addition of the Kapsel Offline OData plugin during build.

"sap.ui5": {...},
"sap.mobile": {
	"definingRequests": {},
	"stores": []
}

 

Configuring the Data Source and the Offline Store

Next, let’s configure the data source and the offline store so that our app initializes an offline store (local DB) based on the OData service.

Data Source

In manifest.json, add a new model and a data source:

		"dataSources": {
			"offlineService": {
				"uri": "/mssampledata/offline/SampleServices/ESPM.svc/",
				"type": "OData",
				"settings": {
					"odataVersion": "2.0",
					"localUri": "localService/metadata.xml"
				}
			}
		}

Add a new route in neo-app.json. Ensure the target name is the same as the cloud platform destination name.

{
  "path": "/mssampledata/offline",
  "target": {
	"type": "destination",
	"name": "mssampledata"
  },
  "description": "Sample Service"
}

Offline Store Creation

The below code snippets, taken from Ludo’s blog, will basically create and open the offline store before loading the component during first initialization.

Open the sap-mobile-hybrid.js file and replace sap.hybrid.startApp with sap.hybrid.openStore

	if ("serverHost" in context && "serverPort" in context && "https" in context) {
			// start SCPms logon
			sap.hybrid.kapsel.doLogonInit(context, appConfig.appID, sap.hybrid.openStore);
		} else {
			console.error("context data for logon are not complete");
		}

Next, let’s prepare the offline store creation in the openStore function:

    openStore: function () {
    	jQuery.sap.require("sap.ui.thirdparty.datajs");
    	var properties = {
    		"name": "offlineService",
    		"host": sap.hybrid.kapsel.appContext.registrationContext.serverHost,
    		"port": sap.hybrid.kapsel.appContext.registrationContext.serverPort,
    		"https": sap.hybrid.kapsel.appContext.registrationContext.https,
    		"serviceRoot": fiori_client_appConfig.appID + "_mssampledata/SampleServices/ESPM.svc/",
    		"definingRequests": {
    			"productsSet": "/Products/?$expand=StockDetails"
    		}
    	};
    	store = sap.OData.createOfflineStore(properties);
    	var openStoreSuccessCallback = function () {
    		sap.OData.applyHttpClient(); //Offline OData calls can now be made against datajs.
    		sap.hybrid.startApp();
    	}
    	var openStoreErrorCallback = function (error) {
    		alert("An error occurred" + JSON.stringify(error));
    	}
    	store.open(openStoreSuccessCallback, openStoreErrorCallback);
    },

A Closer Look

Let’s digress and take a closer look at what’s happening in the openStore function as it is essential in understanding offline store creation.

To create an offline store the sap.OData.createOfflineStore method is used. A ‘properties’ object is passed as an argument and the return type is a sap.OfflineStore object. See sap.OData documentation

There are 2 important parameters in the properties object:

  1. serviceRoot – this identifies the root of an OData service relative to a destination in mobile services. The offline store will be created using the metadata of the OData service which the serviceRoot points to. A service root is unique to an offline store.
  2. Defining Requests – simply put, this property tells the offline store which Entity Sets should be populated with data and be made available offline. In our example above, the offline store will have the Product and Stock data available offline.

Once the offline store is created, it should then be ‘opened’ for offline access. This is done through the sap.OfflineStore.open method. When the offline store is successfully opened, the applyHttpClient and original startApp method are called – more on these in my next blog.

Building the UI

At this stage, our hybrid offline app setup is complete. Let’s create a simple UI for our app.

Let’s create a Stock App that let’s you view the current stock details of a specific product from a Product List. Let’s also add a Supplier List at the bottom.

Home.view.xml

<mvc:View controllerName="zoffline.demo.OfflineDemo.controller.Home" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc"
	displayBlock="true" xmlns="sap.m" xmlns:form="sap.ui.layout.form">
	<App id="idAppControl">
		<pages>
			<Page title="Stok App Offline Demo">
				<content>
					<VBox alignItems="Center">
						<form:SimpleForm id="stockDetailForm" title="Stock Details" layout="ResponsiveGridLayout" labelSpanXL="4" labelSpanL="4" labelSpanM="4"
							labelSpanS="12" adjustLabelSpan="false" emptySpanXL="3" emptySpanL="3" emptySpanM="3" emptySpanS="0" columnsXL="1" columnsL="1" columnsM="1"
							singleContainerFullSize="false">
							<form:content>
								<Label text="Product Id"/>
								<Text text="{offline>ProductId}"/>
								<Label text="Quantity"/>
								<Text text="{path: 'offline>Quantity',  type: 'sap.ui.model.type.Decimal'}"/>
								<Label text="Last Updated"/>
								<Text text="{path: 'offline>UpdatedTimestamp', type: 'sap.ui.model.type.Date', formatOptions: { style: 'short', pattern: 'dd/MM/yyyy'}}"/>
							</form:content>
						</form:SimpleForm>
					</VBox>
					<List items="{offline>/Products}" headerText="Product List" growing="true" growingThreshold="5">
						<ObjectListItem title="{offline>Name}" type="Active" press="onItemPress"
							number="{ parts:[{path:'offline>Price'},{path:'offline>CurrencyCode'}], type: 'sap.ui.model.type.Currency', formatOptions: {showMeasure: false} }"
							numberUnit="{offline>CurrencyCode}">
							<firstStatus>
								<ObjectStatus text="{offline>Category}"/>
							</firstStatus>
							<attributes>
								<ObjectAttribute text="{offline>ProductId}"/>
								<ObjectAttribute text="{offline>LongDescription}"/>
							</attributes>
						</ObjectListItem>
					</List>
						<List items="{offline>/Suppliers}" headerText="Supplier List" growing="true" growingThreshold="5">
						<StandardListItem title="{offline>SupplierName}" />
					</List>
				</content>
			</Page>
		</pages>
	</App>
</mvc:View>

Home.controller.js

sap.ui.define([
	"sap/ui/core/mvc/Controller"
], function (Controller) {
	"use strict";

	return Controller.extend("zoffline.demo.OfflineDemo.controller.Home", {
		onItemPress: function (oEvt) {
			var oContext = oEvt.getSource().getBindingContext("offline");
			this.getView().byId("stockDetailForm").bindElement({
				path: oContext.getPath() + "/StockDetails",
				model: "offline"
			});
			
		}
	});
});

Nothing fancy here, the code is exactly the same as that of an online application. when we access the app on a mobile device or in offline mode, the codebase stays the same, that’s the beauty of Offline OData.

Let’s test it online!

Now let’s make sure the app is working as a webapp in our desktop browser. Run the project as Web Application.

Deploy the Hybrid Application

Time to deploy the project as a hybrid application in Mobile Services.
Right click on the project. Mobile > Build as Packaged App. I will not go into detail as Ludo’s blog already covers this step quite well.

When build is complete, scan the QR code with your device’s QR code reader to download and install the application.

Testing the Application

Open the mobile application and go through the usual login screens. A white screen appears for a few seconds while the offline store is syncing. At this point, the offline store is getting created and the local database tables whose entity sets were defined in the defining request are getting populated with data (remember the openStore function?). After a few seconds, the home screen should load.

Congratulations, you’ve just created an offline application from scratch! Go to airplane mode and test it yourself.

Supplier List empty?

As you can see below, the supplier list does not have any records. This is because we did not include ‘Suppliers’ in the defining requests and hence the Supplier data was not fetched and loaded to the offline store. To fix this, simply add a new defining query for “/Suppliers” in the defining request.

 

Next Steps

We’ve built a simple read-only offline hybrid application from scratch using HAT on SAP Web IDE. In my upcoming blog posts, let’s explore how to implement CRUD, data synchronization, multiple data sources/offline stores and an online/offline scenario. Follow me to stay updated.

Cheers,

Greg

 

Additional Readings

Introduction to Offline OData

JSDoc: sap.OData

 

To report this post you need to login first.

5 Comments

You must be Logged on to comment or reply to a post.

  1. Rakesh Narayan

    Hello Greg – Thanks for the nice blog.

    One Question : Why does the build fail (Build Packaged App) with Error: Build Failed as the CBS build job id was not found for this Application.

    ~ Rakesh Narayan.

    (0) 

Leave a Reply