Technical Articles
Step by Step with the SAP Cloud Platform SDK for Android — Part 5 — Online OData
Previous (Logging) Home Next (Offline OData)
OData stands for Open Data Protocol and is a common way of accessing data from an SAP or other backend using RESTful APIs. The SAP Cloud Platform SDK for Android can be used to generate proxy classes from a backend that provides an OData interface.
The following are some additional sources of documentation on OData and generating and using proxy classes to access OData.
OData Overview
OData Asynchronous APIs
OData Version 2.0
The following instructions demonstrate how to make a data request. Proxy classes that represent the entity types in the of the sample OData service such as Product or SalesOrder will be used.
Generating Proxy Classes
Using the Proxy Classes
Using the Async OData API
Generating Proxy Classes
- Modify the app’s build.gradle to include the OData library.
implementation 'com.sap.cloud.android:odata:2.2.0'
- The metadata document for the service can be viewed with the below URL. Note replace the user id (p1743065160) with your user id.
https://hcpms-p1743065160trial.hanatrial.ondemand.com/mobileservices/origin/hcpms/ESPM.svc/v2/$metadata
Right click the res folder and create a directory named xml. Create a file named metadata.xml.
Copy the content from the url and paste into res/xml/metadata.xml.
If the metadata.xml does not display well, right click on the file in Android Studio and choose Reformat Code.
- Change directories to the root of the project.
cd %USERPROFILE%\AndroidStudioProjects\StepbyStep or on a Mac cd $HOME/AndroidStudioProjects/StepbyStep
- Generate the proxy OData classes by executing the following command in a terminal. The proxy classes will be generated into the project at StepbyStep\app\src\main\java\com\sap\stepbystep.
C:\SAP\AndroidSDK\tools\proxygenerator\bin\proxygenerator -na -m app\src\main\res\xml\metadata.xml -p com.sap.stepbystep.ESPM -d app\src\main\java or on a Mac ~/SAP/AndroidSDK/tools/proxygenerator/bin/proxygenerator -na -m app/src/main/res/xml/metadata.xml -p com.sap.stepbystep.ESPM -d app/src/main/java
Note, the proxygenerator requires a JAVA_HOME environment variable or the java command to be found in your path. The Java JDK can be downloaded from Java SE Downloads.
set JAVA_HOME=C:\Program Files\Java\jdk-11.0.1
- Notice that the proxy classes now appear in the project.
Using the Proxy Classes
- Add the following variables to MainActivity.
private ESPMContainer myServiceContainer; private OnlineODataProvider myDataProvider;
- In the onRegister method, in the updateUICallback, in the response.isSuccessful block, add the below code.
myDataProvider = new OnlineODataProvider("ESPMContainer", serviceURL + "/" + connectionID , myOkHttpClient); myServiceContainer = new ESPMContainer(myDataProvider);
- Add the following class in MainActivity (ie, before the closing }). This class when called will make a request to the OData service to return products using AsyncTask as network tasks cannot be performed on the main thread.
private class ODataQueryTask extends AsyncTask<Void, Void, List<Product>> { @Override protected List doInBackground(Void... voids) { if (myServiceContainer != null) { try { return myServiceContainer.getProducts(); } catch (DataServiceException dse) { Log.d(myTag, "Exception " + dse.getMessage()); } catch (Exception e){ Log.d(myTag, "Exception " + e.getCause().getMessage()); } return Arrays.asList(new Product[0]); } else { Log.d(myTag, "service container is null"); return Arrays.asList(new Product[0]); } } @Override protected void onPostExecute(List<Product> products) { //Can update the UI thread here toastAMessage(products.size() + " products returned"); for (Product product : products) { Log.d(myTag, product.getName()); } } }
- Add the following code to the onOnlineOData method.
new ODataQueryTask().execute();
- Deploy and run the project and examine the Logcat after pressing the Online OData button.
Using the Async OData API
The below example makes use of a query to sort the results, uses an async method, and returns the product names that are in the Notebooks category.
- Regenerate the proxy OData classes, and this time do not include the -na option (noasync) by executing the following command in a terminal.
C:\SAP\AndroidSDK\tools\proxygenerator\bin\proxygenerator -m app\src\main\res\xml\metadata.xml -p com.sap.stepbystep.ESPM -d app\src\main\java or on a Mac ~/SAP/AndroidSDK/tools/proxygenerator/bin/proxygenerator app/src/main/res/xml/metadata.xml -p com.sap.stepbystep.ESPM -d app/src/main/java
Notice that there are now sync and async methods.
- Add the below method.
private void asyncOData() { Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.sap.cloud.mobile.odata"); logger.setLevel(Level.ALL); myDataProvider.getServiceOptions().setCacheMetadata(false); myDataProvider.setTraceRequests(true); myDataProvider.setPrettyTracing(true); myDataProvider.setTraceWithData(true); Log.d(myTag, "In aysncOData"); DataQuery query = new DataQuery() .from(ESPMContainerMetadata.EntitySets.products) .select(Product.name) .where(Product.category.equal("Notebooks")) .orderBy(Product.name); myServiceContainer.getProductsAsync(query, (List<Product> products) -> { toastAMessage(products.size() + " products returned"); for (Product product : products) { Log.d(myTag, product.getName()); } }, (RuntimeException re) -> { toastAMessage("Online OData request failed: " + re.getMessage()); Log.d(myTag, "An error occurred during async query: " + re.getMessage()); }); }
- Modify the onOnlineOData method.
//new ODataQueryTask().execute(); asyncOData();
- The following is a subset of the debug/trace output from the above query. Notice that it contains trace information as well as a sorted list of products in the category of Notebooks.
V/com.sap.cloud.mobile.odata.http.HttpRequest: [AsyncTask #2] [ESPMContainer] Request: GET https://hcpms-p1743065160trial.hanatrial.ondemand.com/com.sap.edm.sampleservice.v2/Products?$select=Name &$filter=(Category%20eq%20'Notebooks')&$orderby=Name V/com.sap.cloud.mobile.odata.http.HttpRequest: [AsyncTask #2] [ESPMContainer] Response: status code = 200, status text = OK, time = 555 ms V/com.sap.cloud.mobile.odata.http.HttpRequest: [AsyncTask #2] [ESPMContainer] Response Headers: ... V/com.sap.cloud.mobile.odata.http.HttpRequest: [AsyncTask #2] [ESPMContainer] Response Content: (pretty printed) ... D/myDebuggingTag: Astro Laptop 1516 Benda Laptop 1408 ITelO FlexTop I4000 ...
- A generated project using the Wizard contains a more complete example that demonstrates create, update and delete operations.
- Another option to generate the proxy classes is to use the OData Plug-in.
Hi Daniel,
thanks for great serie. I'd like to ask you where to get the URL for service's metadata if using custom destination ans service, not the sample backend? In your case it was
https://hcpms-p1743065160trial.hanatrial.ondemand.com/mobileservices/origin/hcpms/ESPM.svc/v2/$metadata
If I click on destination link in APIs section of the app on SCP I get "HTTP Status 403 - Neither Application connection id nor Application id is provided."
Thanks,
Zdenek
I previously wrote a different blog series on our Hybrid SDK. In that series, a custom destination was used. The SCIM parts are so that when the user registers, the provided user name and password are validated against the destination URL.
https://blogs.sap.com/2016/10/20/getting-started-kapsel-part-1-sp13/#adminhcpms
The following section uses the REST API to perform some basic queries against the configuration.
Hope that helps,
Dan van Leeuwen
Thanks Daniel.
Zdenek
Hi,
How can we generate proxy classes using plugin. I’ve tried adding plugins in applevel gradle and module level gradle as per this link
https://help.sap.com/doc/c2d571df73104f72b9f1b73e06c5609a/Latest/en-US/docs/user-guide/getting-started/gradle_overview.html#using-the-odata-plug-in
But I’ve failed to generate it. But I’ve done it with command line tool.
Need little more details about this
One other place you could go for reference is an app generated by the wizard.
See
https://developers.sap.com/tutorials/cp-sdk-android-wizard-app.html
Classes in the internal package would be ones that we would not expect users to call.
I think the generated proxy classes appear in a different location using the steps in this blog because the -d destination parameter was used.
Regards,
Dan van Leeuwen
Hi Daniel Van Leeuwen,
I'm working on online POST request. I'm using createAsync method for callbacks, where if I've passed custom headers, but it was not taking my headers. And I've checked with GET request with the same headers, it was working perfectly. But POST was not the same. Finally After many trails I've removed all the headers and I've passed the registration instance of onlineDataProvider to ESPMContainerService, since then the POST request was executed successfully with no error.
Problem faced:
Every time I open onlineStore to make a POST request it is getting registered with new connectionID / registration ID in HCPms, Need help/suggestion on this.
Are you using the same OKHttpClient instance?
Are you specifying the deviceID when you create the OKHttpClient instance (see the authentication section of this blog series)?
If those two suggestions do not help, I would recommend posting a question using the tag SAP Cloud Platform SDK for Android.
Regards,
Dan van Leeuwen
This solved my problem.
Thanks.
Hello,
I'm using CreateEntityAsync to upload some data... but I also expect to get response from this process after upload, but I found that Action0 Success Listener has empty parameters, So how can I get response?
Thanks.
The following is an example of calling CreateEntityAsync.
There are also a few examples of using the aysnc API's at https://help.sap.com/doc/c2d571df73104f72b9f1b73e06c5609a/Latest/en-US/docs/sample_code/odata/async_apis.html
Hope that helps,
Dan van Leeuwen
Thank you so much for your effort, It worked.
Hello,
I want to wait until the response returns so I do this :
and it works only with getAsync.
but when I do the same thing on createEntityAsync/updateEntityAsync/deleteEntityAsync response doesn't return and system stuck in while loop.
I read on developers guide that:
By default, queries are executed in parallel on Async.THREAD_POOL_EXECUTOR while CUD requests are executed serially on AsyncTask.SERIAL_EXECUTOR. This behavior can be changed by passing in your own executor.
but I don't know how can I change this behavior?
Thanks.
Instead of putting the thread to sleep, could you call the method or add the code that you want to execute after the create succeeds by calling it or placing it in the create success callback?
Perhaps one other thing you could try is an AtomicBoolean.
Regards,
Dan van Leeuwen