Skip to Content
Technical Articles
Author's profile photo Daniel Van Leeuwen

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

  1. Modify the app’s build.gradle to include the OData library.
    implementation 'com.sap.cloud.android:odata:2.2.0'

  2. 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.
  3. Change directories to the root of the project.
    cd %USERPROFILE%\AndroidStudioProjects\StepbyStep
    or on a Mac
    cd $HOME/AndroidStudioProjects/StepbyStep
    
  4. 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
  5. Notice that the proxy classes now appear in the project.

Using the Proxy Classes

  1. Add the following variables to MainActivity.
    private ESPMContainer myServiceContainer;
    private OnlineODataProvider myDataProvider;
  2. 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);
  3. 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());
            }
        }
    }
    
  4. Add the following code to the onOnlineOData method.
    new ODataQueryTask().execute();
        
  5. 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.

  1. 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.

  2. 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());
        });
    }
  3. Modify the onOnlineOData method.
    //new ODataQueryTask().execute();
    asyncOData();
    
  4. 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
    ...
    
  5. A generated project using the Wizard contains a more complete example that demonstrates create, update and delete operations.
  6. Another option to generate the proxy classes is to use the OData Plug-in.

Previous (Logging)   Home   Next (Offline OData)

Assigned tags

      13 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Zdenek Smolik
      Zdenek Smolik

      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

      Author's profile photo Daniel Van Leeuwen
      Daniel Van Leeuwen
      Blog Post Author

      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

       

      Author's profile photo Zdenek Smolik
      Zdenek Smolik

      Thanks Daniel.

      Zdenek

      Author's profile photo hemanth reddy
      hemanth reddy

      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

      odata {
          verbose true
          services {
              products {
                  schemaFile file("src/main/odata/productssvcmetadata.xml")
                  packageName "com.example.products"
              serviceClass ProductsService
              }
              orders {
                  schemaFile file("src/main/odata/orderssvcmetadata.xml")
                  packageName "com.example.orders"
              serviceClass OrdersService
              }
          }
      }

      Edit:---------------------

      After many trials I found the proxy classes generated at this location app\build\generated\source\odata

      Please find the image attached.

      But when I generate through the CMD tool proxyclasses are appeared in app\res\main\java as per our destination. Also could you please brief me about the package (internal)

      Thanks.
      Author's profile photo Daniel Van Leeuwen
      Daniel Van Leeuwen
      Blog Post Author

      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

      Author's profile photo hemanth reddy
      hemanth reddy

      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.

      Author's profile photo Daniel Van Leeuwen
      Daniel Van Leeuwen
      Blog Post Author

      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

      Author's profile photo hemanth reddy
      hemanth reddy

      This solved my problem.

      Thanks.

      Author's profile photo Keroles Nashaat
      Keroles Nashaat

      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.

      Author's profile photo Daniel Van Leeuwen
      Daniel Van Leeuwen
      Blog Post Author

      The following is an example of calling CreateEntityAsync.

      espmContainer.createEntityAsync(salesOrderHeader, () -> {
          Log.d(Utils.tag, "Successfully created the sales order " + salesOrderHeader.getSalesOrderID());
      },
      (error) -> {
          Log.d(Utils.tag, "Failed to create the sales order.  " + error.getMessage());
      });

       

      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

      Author's profile photo Keroles Nashaat
      Keroles Nashaat

      Thank you so much for your effort, It worked.

      Author's profile photo Keroles Nashaat
      Keroles Nashaat

      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.

      Author's profile photo Daniel Van Leeuwen
      Daniel Van Leeuwen
      Blog Post Author

      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