Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
gregcarino
Product and Topic Expert
Product and Topic Expert
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 lnoens

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

 

Part 2 – Implementing CUD, Flush and Refresh



Additional Readings


Introduction to Offline OData

JSDoc: sap.OData

 
32 Comments
jorge_cabanas
Participant
Really good job,

I look forward the second part 🙂



Thank you very much!
Jorge
gregcarino
Product and Topic Expert
Product and Topic Expert
0 Kudos
Glad you liked it Jorge. Thanks for reading!
MikeDoyle
Active Contributor
0 Kudos
Nice debut, Greg, it's great to see you sharing your expertise.  The first of many great blog posts I hope!
gregcarino
Product and Topic Expert
Product and Topic Expert
0 Kudos
Thanks Mike! More to come 🙂
rakeshnarayan
Explorer
0 Kudos

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.

gregcarino
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi Rakesh,

Thanks for reading and apologies for the late response. Is this still an issue? I haven't seen this error before, might be a mobile services hiccup. Let me know how i can help if the issue still persists.

Cheers
0 Kudos
Hi greg_carino

I can filter data from the entity before download to device
gregcarino
Product and Topic Expert
Product and Topic Expert
Hi Angel,

Yes you can add OData query options in the defining request to filter data you download to the device instead of the whole data set, for example the defining request below only downloads records with category 'Notebooks'. Keep in mind that the defining request cannot be changed once the offline store is opened, and that the same query (with filter options) will be called when you do a refresh.
"definingRequests": {
"productsSet": "/Products/?$filter=Category eq 'Notebooks'
}
former_member188396
Participant
0 Kudos
Hi Greg,

 

Very nice blog!!

 

Regarding the offline capability, will it still work in below scenario?

 

  1. I have downloaded the app on the device and access it first time. this time it will go through the normal login process and downloads the data.

  2. Device is turned off on that day.

  3. Next day, I start the device and there is no internet connection.

  4. At this time, when I launch the app in device, will it again go through login process or will it continue working based on the data downloaded yesterday and keep working with the app?


I have read in your blog as well as in Ludo's blog that once we have opened the app and login is completed, THEN device is gone to offline mode to keep working on it.

However, my question is, when device is on airplane mode, can I launch the app without login process and based on the previously stored data, will I be able to continue with the app?

 

Thanks,

Bhavik
gregcarino
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi Devisha,

Thanks for reading.

Yes, when there's no internet connection the app is opened immediately with the previously stored data in the offline store. When online, the login process is triggered when the user registration has expired - you can configure and delete registrations in mobile services.

Cheers,

Greg
former_member188396
Participant
0 Kudos
Thanks for the quick response.

 

So, If I read it correctly, in the event of no internet connection, App skips the login process and let user to work with based on the previously saved data. Once online, it can detect and ask user to login and then sync the data.

 

Will this be handled automatically through Mobile service or do we have to put actual code in the app to make it work that way?

 

Thanks,

Bhavik
0 Kudos

Hi greg_carino

 

How do i can to disable or hide Passcode Screen. 

former_member188396
Participant
Hi Greg,

 

I tried doing this POC. However, when I try to Build the packaged App, it is giving me an error saying: "Fail to get minimum OS versions:"

And dropdown for Minimum OS version is empty. It is not letting me go further without this information.

 

Not sure where do we need to maintain this information?



Thanks,

Bhavik
gregcarino
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi Angel,

Go to your app config in Mobile Services and go to Client Policies. There is a checkbox there to disable the passcode screen.

Greg
0 Kudos

I have found the option enable Passcode policy. I have unchecked that option, but that no function when i generate and install my app in device.

 

former_member609456
Discoverer
0 Kudos

Hello Greg

I have a problem when trying to enable the project as “Hybrid Mobile Project”. Why can this be?

I have tried to make a new project from scratch, make another workspace, turned on/off the HAT extension, and even deleted my trial account.

The “Mobile” option is only visible right after refreshing the browser. However it is empty, and disappears when right clicking the project another time.

 

Thanks!

former_member609456
Discoverer
0 Kudos
Resolved the issue!

In Cloud Cockpit > Destinations, the "mobileservices" were missing.
0 Kudos

Hi Grep.

Have you got any idea about filter  to range dates in the defining request?

0 Kudos
Hi Grep.

Do you know to keep the session active in offline app. Currently, the app show screen login when if i inactive for 5 min.
gregcarino
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi Angel,

 

This is because of SAML expiry with SAP ID service or your Identity Provider. I would recommend you to use OAuth security as the authentication method to keep the session active.

If you can modify your IdP, then you can consider increasing the expiry of the SAML token.

 

Regards,

Greg
0 Kudos

Hi Greg.

Thank so much. Could you please share the steps for implement that.

 

Regards,

Angel

0 Kudos

Hello Greg,

I need you helping with this error

 

 

Regards,

Angel

0 Kudos

Hello Greg.

 

my application sometimes do not sync the data. But in the console show this message “MAF LogonCoreCDVPlugin: Pause event successfully set.”

 

 

Regards,

Angel

jaffa
Discoverer
0 Kudos
Hi Greg,

is the architecture for such a hybrid app like described in this article?

Especially this part: "Even when the device has network connectivity, your application will not access data that is part of the offline store directly from the back-end server but will use the local database."

Regards, Andrea
gregcarino
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi Andrea,

 

Yes you are correct. Once the app is offline-enabled, all calls to the service will be routed to the offline store instead (with or without connectivity). This ensures your offline store is always in sync.

You can also check part 2 of my blog for more information on how the offline store store works.

 

Cheers,

Greg
0 Kudos
Hi Greg,

 

Please, could you help me this error?



Regards, Angel.
0 Kudos
Hi Greg, thanks for sharing your expertise! Fantastic blog!

Just a quick question. i would like to use an OData filter in the defining requests, with the filter parameters being a variable e.g. an username returned from the userapi. Will this be possible or am I stuck with fetching the entire data set?

Best regards,

Frank
0 Kudos

I am using the sampleservices product set and facing an issue while doing a create. The model.create method goes into sucess callback but the success object is returned as blank

the value for all properties is returned as null despite sending the entered values correctly during create call. Hence i am seeing blank line item getting added into the master list of products.

 

Same entry if create from desktop version adds an entry correctly

former_member688370
Discoverer
0 Kudos

Hi Greg! Hope you doing well.

 

So, I have the following scenario. I have a service that contains some EntitySets. But, I wanna only one of these sets to be available offline. The other ones I want to be requested only on a online scenario.

 

But, as you explained for the Suplier list, the “online” sets are not specified on defining requests and then they aren't available on the app.

 

So, any idea to solve this?

 

Best regards,

 

André

gregcarino
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi Andre,


There are many ways to achieve this.

An approach that I can recommend that doesn't require a lot of handling during runtime is to define two models that point to the same Odata service: one for offline calls and another for online-only calls.

Note that you need two separate routes in neo-app.json. Using my example in the blog, you need to add another another path for "/mssampledata/online".

Hope this helps,

Greg
former_member688370
Discoverer
0 Kudos
Hi Greg, how are you?

Your tip worked really well. Really thanks for the help ?

Greetings,

André
SaurabhN
Participant
0 Kudos
Hi greg_carino

I am unable to load application. Device ends up showing white screen.

 

appRouters.js
var mobile_appRoutes = [
{
"path": "/services/userapi",
"manual": true
},
{
"path": "/odata_destn",
"destination": "odata_destn"
}
];

sap-mobile-hybrid.js
var userModel = new sap.ui.model.json.JSONModel("/services/userapi/attributes");
var properties = {
"name": "store_mainService",
"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 + "_" + mobile_appRoutes[1].destination +
"/odata/namespace/odataservicename;v=1/",
"definingRequests": {
"Customers": "/Customer(userid='" + oUserModel.getData().uid + "')/Set"
}
};

Destinations:


Labels in this area