Skip to Content

What’s new
  • [2013-03-21] Adding zipped source code and 2 screen shots

Introduction

Sorry, for waiting so long to get this 2nd blog out – there are so many things to be done…
OK, after we have successfully created our first native Android app on a HANA One instance in 20 minutes by following these steps (http://scn.sap.com/blogs/jan_t/2013/02/16/how-to-create-an-android-app-on-sap-hana-one-in-7-steps), I now want to get a little bit further. I want to:
  • parse the json format which was returned from HANA XS in order to
  • show results in a little bit nicer way (just readable)
    • Remarks:
      • I know that a real Android app would look much more appealing.
      • I know that date handling and error handling both require a lot more sophistication.
      • For the sake of brevity I keep it like this – it’s not the focus of this blog.
  • use more features of the XS odata API to select some more meaningful data: query filters, input parameters, …
As the focus in this blog is again on how to leverage SAP HANA, I compare 2 techniques to get data and calculate results:
    1. Frontend approach: I fire several odata service calls in 1 http request and do some math with the results in the Android device. (~ 15 min)
    2. Backend approach: I use a Calculation View to get data and calculate the correct results in HANA (~ 10 min)
And all of that in 15 – 25 minutes again. Let’s see how it works.

Prerequisites

I had a problem in XS engine until I upgraded my local HANA server to Revision >= 48. HANA One on AWS now is on Revision 48.

Further Readings

For more information on what SAP HANA Extended Application Services (HANA XS) offer please read http://help.sap.com/hana/hana_dev_en.pdf

Source Code Upload

If it is too cumersome for you to make the code changes proposed below, then use the zipped content in Dropbox:
There are 4 parts in it:
  • XS project “androidaponhana” – import it via:
    • right-click in left-most “Project” area -> New -> Project -> SAP HANA Development Project -> XS Project -> Next -> Project Name: <new project name>
    • -> File -> Import… -> General -> File System -> From directory: <directory> -> check “androidapponhana” -> Into Folder: <new project name> -> when asked “Overwrite .project in <new project name>?” select “Yes To All”
    • Then, right-click your project -> Team -> Share Project… -> select your <repository workspace> -> Finish
    • Team -> Commit   and   -> Team -> Activate
  • Calculation View is in the HANA Content and therefore not part of the XS project. Therefore, import “XSE” via:
    • -> File -> Import… -> SAP HANA Content -> Developer Mode -> Next -> <Select vour system> -> Folder location “XSE” -> Objects for import -> Content -> androidapphana -> calculation views -> COMPARE_STOCK_CV.calculationview -> add it to the right -> Finish
  • Import the 2 Android projects Frontend / Backend via:
    • -> File -> Import -> Android -> Existing Android Code into Workspace -> Root Directory <path\>AndroidAppOnHANAExtensionBackendApproach -> Finish
    • -> File -> Import -> Android -> Existing Android Code into Workspace -> Root Directory <path\>AndroidAppOnHANAExtensionFrontendApproach -> Finish

That is it from a source code point of view. You still have to create some test data. By the way, this is an export of my implementation containing my users, passwords, IP addresses etc. Please change them as described below!!!!!! It won’t work otherwise!

Also, there is a Frontend Learning and a Backend Learning section below. Don’t forget to read them!

Enhance the Android App for the Frontend Approach

Copy project AndroidAppOnHANABaseScenario to AndroidAppOnHANAExtensionFrontendApproach. Run it as Android Application once to see if it still works.
Below, the existing parts are shown in grey, whereas the new parts are shown in blue, to be deleted parts in orange:
Then let’s do the UI xml files first.
Open -> res -> layout -> activity_show_market_price_data.xml (xml layout, not the Graphical one) and replace the whole existing content by this one (which will show a table layout with some input/ output fields and a button in between):
<TableLayout
   xmlns:android=”http://schemas.android.com/apk/res/android
android:id=”@+id/user_table_1″
    android:layout_width=”fill_parent”
   android:layout_height=”fill_parent” >
      
   <TableRow
    android:id=”@+id/user_table_item_row1″
  android:layout_width=”fill_parent”
     android:layout_height=”wrap_content”
  android:paddingTop=”3dp”
     >
       <TextView
         android:id=”@+id/TextDisplay1″
      android:layout_width=”0dp”
      android:layout_weight=”1.0″
      android:layout_height=”wrap_content”
      android:singleLine=”true”
         android:text=”@string/stock1″ />
      
       <EditText
         android:id=”@+id/nsin1″
      android:layout_width=”0dp”
      android:layout_weight=”1.0″
      android:layout_height=”wrap_content”
      android:singleLine=”true”
      android:textAppearance=”@android:style/TextAppearance.Medium”
      android:paddingLeft=”3dp”
      android:gravity=”left|center_vertical”
         android:inputType=”text” >
     </EditText>
   </TableRow> 
   <TableRow
    android:id=”@+id/user_table_item_row2″
  android:layout_width=”fill_parent”
     android:layout_height=”wrap_content”
  android:paddingTop=”3dp”
     >
       <TextView
         android:id=”@+id/TextDisplay2″
      android:layout_width=”0dp”
      android:layout_weight=”1.0″
      android:layout_height=”wrap_content”
      android:singleLine=”true”
         android:text=”@string/stock2″ />
      
       <EditText
         android:id=”@+id/nsin2″
      android:layout_width=”0dp”
      android:layout_weight=”1.0″
      android:layout_height=”wrap_content”
      android:singleLine=”true”
      android:textAppearance=”@android:style/TextAppearance.Medium”
      android:paddingLeft=”3dp”
      android:gravity=”left|center_vertical”
         android:inputType=”text” >
     </EditText>
   </TableRow> 
   <TableRow
    android:id=”@+id/user_table_item_row3″
  android:layout_width=”fill_parent”
     android:layout_height=”wrap_content”
  android:paddingTop=”3dp”
     >
     <EditText
         android:id=”@+id/start_date”
      android:layout_width=”0dp”
      android:layout_weight=”1.0″
      android:layout_height=”wrap_content”
      android:singleLine=”true”
         android:inputType=”text|date” />
       <TextView
         android:id=”@+id/TextDisplay4″
      android:layout_width=”0dp”
      android:layout_weight=”1.0″
      android:layout_height=”wrap_content”
      android:singleLine=”true”
         android:text=”@string/end_text” />
     <EditText
         android:id=”@+id/end_date”
      android:layout_width=”0dp”
      android:layout_weight=”1.0″
      android:layout_height=”wrap_content”
      android:singleLine=”true”
         android:inputType=”text|date” />
   </TableRow> 
   <TableRow
    android:id=”@+id/user_table_item_row4″
  android:layout_width=”fill_parent”
     android:layout_height=”wrap_content”
  android:paddingTop=”3dp”
     >
      <Button
         android:id=”@+id/button”
      android:layout_width=”0dp”
      android:layout_weight=”1.0″
      android:layout_height=”wrap_content”
         android:onClick=”calculate”
         android:text=”@string/button” /> 
        
</TableRow> 
   <TableRow
    android:id=”@+id/user_table_item_row5″
  android:layout_width=”fill_parent”
     android:layout_height=”wrap_content”
  android:paddingTop=”3dp”
     >
     <GridView
         android:id=”@+id/gridView1″
      android:layout_width=”0dp”
      android:layout_weight=”1.0″
      android:layout_height=”wrap_content”
         android:numColumns=”2″ >
     </GridView>
   </TableRow> 
</TableLayout>
Then, go to -> res -> values -> strings.xml and replace the existing content by the following:
<?xml version=”1.0″ encoding=”utf-8″?>
<resources>
    <string name=”app_name”>Compare 2 stocks!</string>
   
    <string name=”menu_settings”>Settings</string>
 
     <string name=”stock1″>Stock 1</string>
     <string name=”stock2″>Stock 2</string>
     <string name=”start_text”>since</string>
     <string name=”end_text”>through</string>
     <string name=”nsin1″>716460</string>
     <string name=”nsin2″>LU0173001990</string>
     <string name=”button”>Compare</string>
     <string name=”result”>Performance of </string>
   
     <string-array name=”textContent”>
        <item>Stock 1</item>
        <item>Stock 2</item>
        <item>000716460</item>
        <item>dummy</item>
        <item>LU0173001990</item>
        <item>01/01/2013</item>
        <item>today</item>
        <item>Performance of </item>
     </string-array>
    
</resources>
Then, let’s enhance the coding. Go to -> src -> com.example.androidapponhanabasescenario -> ShowMarketPriceData.java.
At first, you will need some more libraries:
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import android.widget.EditText;
import java.util.Calendar;
import java.text.SimpleDateFormat;
import java.io.InputStream;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicHeader;
import org.odata4j.core.Guid;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.james.mime4j.util.CharsetUtil;
import java.io.UnsupportedEncodingException;
import java.io.IOException;
import android.util.Log;
Now, for some of them you need to download additional jar files from the internet. You need to download
and put them into libs directory in eclipse as described in the last blog.
Enhance your class with some class attributes:
  • public class ShowMarketPriceData extends Activity   {
JSONArray results = null;
GridView gvMain;
ArrayAdapter<String> adapter;
private ShowMarketPriceData mContext;
final List<String> performance = new ArrayList<String>();
In the onCreate method, add an initialization method call and its definition as well as a calculation method (to react on a button click):
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initialize(this);
….
  • delete
    ShowDialogAsyncTask aTask = new ShowDialogAsyncTask();
    aTask.execute();
  • create
public void initialize(ShowMarketPriceData Context) {                   
mContext = Context;
gvMain = (GridView) findViewById(R.id.gridView1);

EditText nsin1Field = (EditText) findViewById(R.id.nsin1);
EditText nsin2Field = (EditText) findViewById(R.id.nsin2);
EditText startDate = (EditText) findViewById(R.id.start_date);
EditText endDate = (EditText) findViewById(R.id.end_date);

// Get today as a Calendar 
Calendar today = Calendar.getInstance(); 
// Make an SQL Date out of that 
java.sql.Date todayText = new java.sql.Date(today.getTimeInMillis()); 
// Subtract 1 day 
Calendar month_ago = Calendar.getInstance(); 
month_ago.add(Calendar.DATE, -31); 
// Make an SQL Date out of that 
java.sql.Date monthAgoText = new java.sql.Date(month_ago.getTimeInMillis()); 

SimpleDateFormat sdf = new SimpleDateFormat( “yyyy/MM/dd” );

String[] texts = getResources().getStringArray(R.array.textContent);

nsin1Field.setText( texts[2] );   
nsin2Field.setText( texts[4] );
startDate.setText( sdf.format( monthAgoText ));     
endDate.setText( sdf.format( todayText ));

}
public void calculate(View view) {
ShowDialogAsyncTask aTask;
aTask = new ShowDialogAsyncTask();
aTask.execute();
}
  • delete the orange parts and replace them
public String getOdata() {
String JASONrs = “You will get there!”;

try {

   hanacon = myhana.openConnection();
   hanacon.setReadTimeout(1000);
   hanacon.setConnectTimeout(1000);

   String userpass = “SYSTEM” + “:” + “manager”;
//   String userpass = “SYSTEM” + “:” + “Hana2012”;
   String basicAuth = “Basic ” + new String(Base64.encode(userpass.getBytes(), 0));
   hanacon.setRequestProperty (“Authorization”, basicAuth);

   BufferedReader in = new BufferedReader(new InputStreamReader(hanacon.getInputStream()));
   String inputLine;
   while ((inputLine = in.readLine()) != null)
    JASONrs += inputLine;
   in.close();
  } catch (Exception e) {
   e.printStackTrace();
   return “Error”;
  }

  return JASONrs;
}

  • so, replace the orange parts with this
InputStream is = null;
//——————————– for http watching
//      Properties props = System.getProperties();
//      props.put(“http.proxyHost”, “<my laptop’s IP address>”); //
//      props.put(“http.proxyPort”, “8888”);
//——————————–

     EditText nsin1Field = (EditText) findViewById(R.id.nsin1);
     EditText nsin2Field = (EditText) findViewById(R.id.nsin2);
     EditText start_date = (EditText) findViewById(R.id.start_date);
     EditText end_date   = (EditText) findViewById(R.id.end_date);

     String nsin1String = nsin1Field.getText().toString();
     String nsin2String = nsin2Field.getText().toString();
     String startDateString = start_date.getText().toString();
     String endDateString = end_date.getText().toString();

     try {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        httpClient.getCredentialsProvider().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(“<HANA user name>”, “<your pw>”));
        httpClient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, System.getProperty(“http.agent”));

        HttpPost httpPost = new HttpPost(“http://<hana.server.name>:80<HANA_instance_number>/androidapphana/StockDataIF.xsodata/$batch“);

        httpPost.addHeader(new BasicHeader(“Accept”, “*/*”));
        httpPost.addHeader(new BasicHeader(“Accept-Charset”, “ISO-8859-1,utf-8;q=0.7,*;q=0.3”));
        httpPost.addHeader(new BasicHeader(“Accept-Encoding”, “gzip,deflate,sdch”));
        httpPost.addHeader(new BasicHeader(“Accept-Language”, “de-DE.de;q=0.8,en-US;q=0.6,en;q=0.4”));

        String batchBoundary = “batch_” + Guid.randomGuid().toString();
        String batchMultipartBoundary = “multipart/mixed; boundary=” + batchBoundary;
        httpPost.addHeader(new BasicHeader(“Content-Type”, batchMultipartBoundary));

        httpPost.addHeader(new BasicHeader(“Host”, “<hana.server.name>:80<HANA_instance_number>”));

        String starter = “\r\n \r\n–” + batchBoundary + “\r\n”;
        String CS = “Content-Type: application/http\r\nContent-Transfer-Encoding:binary\r\n\r\nGET /History/?$top=1&$filter=NSIN%20eq%20′”;
        String DC = “‘&$orderby=DATE%20desc&$select=DAY_CLOSE&$format=json HTTP/1.1\r\nAccept:application/json\r\n\r\n “;

        String sb1 = CS + nsin1String  + “‘%20and%20DATE%20le%20datetime'” + startDateString + DC;
        String sb2 = CS + nsin1String  + “‘%20and%20DATE%20le%20datetime'” + endDateString + DC;
        String sb3 = CS + nsin2String + “‘%20and%20DATE%20le%20datetime'” + startDateString + DC;
        String sb4 = CS + nsin2String + “‘%20and%20DATE%20le%20datetime'” + endDateString + DC;

        String total_string = starter + sb1 + starter + sb2 + starter + sb3 + starter + sb4;
        StringBody all = new StringBody(total_string, “dummy”, CharsetUtil.ISO_8859_1);

         MultipartEntity multipartContent = new MultipartEntity(HttpMultipartMode.STRICT, batchBoundary, CharsetUtil.ISO_8859_1);
          multipartContent.addPart(“GET”, all);

         httpPost.setEntity(multipartContent);

         HttpResponse httpResponse = httpClient.execute(httpPost);
         HttpEntity httpEntity = httpResponse.getEntity();

         is = httpEntity.getContent();

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
     }

        try {
           BufferedReader reader = new BufferedReader(new InputStreamReader(is, “iso-8859-1”), 8);
           StringBuilder sb = new StringBuilder();
           String line = null;
           while ((line = reader.readLine()) != null) sb.append(line + “\n”);
           is.close();

           JASONrs = sb.toString();

  • Make sure to replace “<HANA user name>” (for instance “SYSTEM”) and <your pw> (for instance “manager”)
  • Also make sure to replace “<hana.server.name>:80<HANA_instance_number>” with your HANA system (for instance “23.20.228.188:8000”)
  • Then, enhance ShowDialogAsyncTask like this:
    • delete
TextView tv = (TextView) findViewById(R.id.MyTextResponse);
   //Object obj=JSONValue.parse(result); 
   tv.setText(result);
    • create

        @Override
        protected void onPostExecute(String result) {

List<String> JSONrs = new ArrayList<String>();
         String day_close = “”;
   String json_strings[];
   // split the 1 resturn string into the 4 json strings (we had 4 requests initially)
   JSONrs.clear();
   performance.clear();
   json_strings = result.split(“\”d\”:”);
   int i;
   for(i=0; i<json_strings.length – 1; i++) {   
           json_strings[i] = “{\”d\”:” + json_strings[i+1].substring(0, json_strings[i+1].lastIndexOf(“}”)+1);
   }
   json_strings[i] = null;
   i=0;

  String[] texts = getResources().getStringArray(R.array.textContent);

  // parse the json objects
  try {
        while ((json_strings[i]) != null) {
        try {
            JSONObject jsn =  new JSONObject( json_strings[i] ); 

            JSONObject c = jsn.getJSONObject(“d”);
            JSONArray results = c.getJSONArray(“results”);

            for(int j = 0; j < results. length(); j++){
                JSONObject res = results.getJSONObject(j);
                day_close = res.getString(“DAY_CLOSE”);

             }
            JSONrs.add(day_close);

         } catch (JSONException e) {
                  e.printStackTrace();
         }
         i++;
     }

} catch (Exception e) {
       e.printStackTrace();
}

    try {

    for(int j = 0; j < 4; j++){

           Float f= ( Float.valueOf(JSONrs.get(j+1)) / Float.valueOf(JSONrs.get(j)) * 100 ) – 100;

           performance.add(texts[7] + ” ” + texts[j + 2]);

           performance.add(f.toString());

 

           j++;

    }

    } catch (IndexOutOfBoundsException e) {

           performance.add(“no results”);

   } 

if (gvMain.getAdapter() == null) {
             adapter = new ArrayAdapter<String>(mContext, android.R.layout.simple_list_item_1, performance);
            gvMain.setAdapter(adapter);
} else
             adapter.notifyDataSetChanged();

  • OK, you’re done. But before you can test you need to create some more test data. Type and execute in HANA Studio (like described in last blog):
    • insert into “COMPARE_STOCK”.”androidapponhana::PRICE_HISTORY” ( NSIN, DATE, TIME, DAY_OPEN, DAY_HIGH, DAY_LOW, DAY_CLOSE, VOLUME ) values (‘000716460’, ‘20130209’, ‘130000’, 60.01, 59.55, 57.91, 60.1, 2313291);
    • insert into “COMPARE_STOCK”.“androidapponhana::PRICE_HISTORY” ( NSIN, DATE, TIME, DAY_OPEN, DAY_HIGH, DAY_LOW, DAY_CLOSE, VOLUME ) values (‘000716460’, ‘20130313’, ‘130000’, 61.01, 62.55, 61.91, 63.1, 2313291);
    • insert into “COMPARE_STOCK”.”androidapponhana::PRICE_HISTORY” ( NSIN, DATE, TIME, DAY_OPEN, DAY_HIGH, DAY_LOW, DAY_CLOSE, VOLUME ) values (‘LU0173001990’, ‘20130209’, ‘130000’, 59.19, 59.55, 58.91, 59.2, 2313291);
    • insert into “COMPARE_STOCK”.”androidapponhana::PRICE_HISTORY” ( NSIN, DATE, TIME, DAY_OPEN, DAY_HIGH, DAY_LOW, DAY_CLOSE, VOLUME ) values (‘LU0173001990’, ‘20130313’, ‘130000’, 60.19, 60.55, 59.91, 60.2, 2313291);
  • Important when testing in the future: When making these INSERTs into the table, please use the following inputs in the Android App:
    • start date: after March 9th, 2013
    • end date: between February 9th and March 9th.
    • Othewise you won’t see anything!!!
  • OK. This is it. Run it and click the “Compare” button. This is what it should look like:

Android2.png

What did we learn?

  • Of course, it is possible to send several asynchronous http GET requests to HANA, get the data and calculate the result. This is a bad idea though, as “backend calls” via mobile connections may have a long runtime.
  • So, a “batch request” of several http GET requests combined into 1 Multipart http POST request is a better way of doing this. HANA XS requires a certain format which is described in the developer documentation. It took me some time to learn how to get my Android code into a shape that is working correctly. Still, there is an unnecessary “dummy” part which I didn’t manage to get rid of and I simply ignore. I leave this as a task for Multipart experts.
    • Accordingly, the parsing of the JSON leads to a little bit weird numbering in the for loop. Sorry. You might find more perfect solutions.
    • Interestingly enough, we learn how odata parameters are used in HANA XS.
  • Furthermore, please note that the exact json object that is tailored by the XS engine according to my table design in HANA is parsed here (objects “d”, “results”, “DAY_CLOSE” – especially that is part of my HANA table design). So, here we learn how to change this for use in other XS services and with other tables.
  • Finally, the math that is being done with 4 different query results is pretty simple:
    • Float f= ( Float.valueOf(JASONrs[j+1]) / Float.valueOf(JASONrs[j]) * 100 ) – 100;
    • I calculate the performance of a stock of date 2 on the basis of its value at
      date 1 in percent of change (be it pos or neg).
    • Comparing the 2 technologies with the same “mathematical problem” we learn their strengths and what it needs to use them.

Enhance the Android App for the Backend Approach

In this approach, we are again focusing on the HANA side and are going to create some more HANA artifacts. But let’s first adapt our Android code.

Adapt the Android App

In case you are starting form here: keep in mind that instead of copying from the ForntendApproach project you can copy from the BaseScenario project, but then you must do the stuff with activity_show_market_price_data.xml / strings.xml / imports / class attributes as described above.
Copy project AndroidAppOnHANAExtensionFrontendApproach to AndroidAppOnHANAExtensionBackendApproach.
  • Basically revert most the changes in the code of getOdata(). We will be using HTTP GET again in this approach as most fo the work is being done in the backend. So, we only make 1 call to the backend. Go to -> src -> com.example.androidapponhanabaseScenario -> ShowMarketPriceData.java.
delete the orange part:

public String getOdata() {
String startDateString = start_date.getText().toString();
String endDateString = end_date.getText().toString();
     try {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        httpClient.getCredentialsProvider().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(“<HANA user name>”, “<your pw>”));
        httpClient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, System.getProperty(“http.agent”));

        HttpPost httpPost = new HttpPost(“http://<hana.server.name>:80<HANA_instance_number>/androidapphana/StockDataIF.xsodata/$batch“)

        httpPost.addHeader(new BasicHeader(“Accept”, “*/*”));
        httpPost.addHeader(new BasicHeader(“Accept-Charset”, “ISO-8859-1,utf-8;q=0.7,*;q=0.3”));
        httpPost.addHeader(new BasicHeader(“Accept-Encoding”, “gzip,deflate,sdch”));
        httpPost.addHeader(new BasicHeader(“Accept-Language”, “de-DE.de;q=0.8,en-US;q=0.6,en;q=0.4”));

        String batchBoundary = “batch_” + Guid.randomGuid().toString();
        String batchMultipartBoundary = “multipart/mixed; boundary=” + batchBoundary;
        httpPost.addHeader(new BasicHeader(“Content-Type”, batchMultipartBoundary));

        httpPost.addHeader(new BasicHeader(“Host”, “<hana.server.name>:80<HANA_instance_number>”))

        String starter = “\r\n \r\n–” + batchBoundary + “\r\n”;
        String CS = “Content-Type: application/http\r\nContent-Transfer-Encoding:binary\r\n\r\nGET /History/?$top=1&$filter=NSIN%20eq%20′”;
        String DC = “‘&$orderby=DATE%20desc&$select=DAY_CLOSE&$format=json HTTP/1.1\r\nAccept:application/json\r\n\r\n “;

        String sb1 = CS + nsin1String  + “‘%20and%20DATE%20le%20datetime'” + startDateString + DC;
        String sb2 = CS + nsin1String  + “‘%20and%20DATE%20le%20datetime'” + endDateString + DC;
        String sb3 = CS + nsin2String + “‘%20and%20DATE%20le%20datetime'” + startDateString + DC;
        String sb4 = CS + nsin2String + “‘%20and%20DATE%20le%20datetime'” + endDateString + DC;

        String total_string = starter + sb1 + starter + sb2 + starter + sb3 + starter + sb4;
        StringBody all = new StringBody(total_string, “dummy”, CharsetUtil.ISO_8859_1);

         MultipartEntity multipartContent = new MultipartEntity(HttpMultipartMode.STRICT, batchBoundary, CharsetUtil.ISO_8859_1);
          multipartContent.addPart(“GET”, all);

         httpPost.setEntity(multipartContent);

         HttpResponse httpResponse = httpClient.execute(httpPost);
         HttpEntity httpEntity = httpResponse.getEntity();

         is = httpEntity.getContent();

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
     }

        try {
           BufferedReader reader = new BufferedReader(new InputStreamReader(is, “iso-8859-1”), 8);
           StringBuilder sb = new StringBuilder();
           String line = null;
           while ((line = reader.readLine()) != null) sb.append(line + “\n”);
           is.close();

           JASONrs = sb.toString();

  • instead, create these lines
  try {
   URL myhana = new URL(
    “http://<hana.server.name>:80<HANA_instance_number>/StockDataIF.xsodata/” +
     “AppParams(NSIN1='” + nsin1String + “‘,NSIN2='” + nsin2String +
     “‘,START_DATE=datetime'” + startDateString + “T00:00:00.0000000’,” +
     “END_DATE=datetime'” + endDateString + “T00:00:00.0000000′)/AppResults?$format=json”);
   URLConnection hanacon;
   hanacon = myhana.openConnection();
   hanacon.setReadTimeout(1000);
   hanacon.setConnectTimeout(1000);
   String userpass = “<HANA user name>” + “:” + “<your pw>”;
   String basicAuth = “Basic ” + new String(Base64.encode(userpass.getBytes(), 0));
   hanacon.setRequestProperty (“Authorization”, basicAuth);
   BufferedReader in = new BufferedReader(new InputStreamReader(
     hanacon.getInputStream()));
   String inputLine;
   while ((inputLine = in.readLine()) != null)
    JASONrs += inputLine;
   in.close();
  • Also make sure to replace “<hana.server.name>:80<HANA_instance_number>” with your HANA system (for instance “23.20.228.188:8000”)
  • Make sure to replace “<HANA user name>” (for instance “SYSTEM”) and <your pw> (for instance “manager”)
  • Then, adapt the JSON parsing. Now, we don’t get the raw data (table column “DAY_CLOSE”) but we get the calculated result instead (“NSIN_GROWTH”), 2 lines of them, exactly 2 ones we want to show on the UI.
    • delete the orange lines
private class ShowDialogAsyncTask extends        AsyncTask<Void , Void , String>{
              for(int j = 0; j < results. length(); j++){
                  JSONObject res = results.getJSONObject(j);
                  day_close = res.getString(“DAY_CLOSE”);
              }
          JSONrs.add(day_close);
    • and replace them by these lines
              for(int j = 0; j < results. length(); j++){
                  JSONObject res = results.getJSONObject(j);
                 JSONrs.add(res.getString(“NSIN_GROWTH”));
              }
  • Finally, replace the way how to combine strings for display.
    • delete these lines
  for(int j = 0; j < 4; j++){
         Float f= ( Float.valueOf(JSONrs.get(j+1)) / Float.valueOf(JSONrs.get(j)) * 100 ) – 100;
         performance.add(texts[7] + ” ” + texts[j + 2]);
         performance.add(f.toString());
         j++;
  }
    • and replace them by these lines in blue

    try {

         performance.add(texts[7] + ” ” + texts[2]);

         performance.add(JSONrs.get(0));

         performance.add(texts[7] + ” ” + texts[4]);

         performance.add(JSONrs.get(1));

    } catch (IndexOutOfBoundsException e) {

        performance.add(“no results”);

    }

This is it on the Android side. Now, we can’t test this yet as we are now missing the HANA Calculation which we are calling out of the Android code.
So, let’s get this done in the HANA Studio.

Adapt the HANA XS App

Start your HANA Studio. Connect to your HANA (as described in the last Blog).
  • It is important to do this first: create the CalcView before adapting the xsodata service definition! (There is a dependency chain.) Go to the SAP HANA Development perspective and go to the Navigator tab in the leftmost pane this time. Navigate to -> Content -> androidapponhana -> right-click Calculation Views and click New…
  • Put the Name “COMPARE_STOCK_CV”, select view type SQL Script, leave everything else as defaulted and click Finish. You will get a graphical CalculationView development canvass with 3 panes. (May-be you have to click in the middle of the Ouput box in the left-most of the three panes first.)
  • Right-click Input Parameters in the righ-most pane and click New… Create 4 input parameters:
    • Name “NSIN1”, Data Type = NVARCHAR, length = 12, enable “Is Mandatory”.
    • Name “NSIN2”, Data Type = NVARCHAR, length = 12, enable “Is Mandatory”.
    • Name “START_DATE”, Type = Date, enable “Is Mandatory”.
    • Name “END_DATE”, Type = Date, enable “Is Mandatory”.
  • Then, click in the middle of the Script box in the left-most of the three panes and put this SQL Script code:
/********* Begin Procedure Script ************/
BEGIN
     var_out = select top 1 ( 100 * DAY_CLOSE / ( select top 1 DAY_CLOSE from “COMPARE_STOCK”.”androidapphana::PRICE_HISTORY” 
  where NSIN = :NSIN1
  and   DATE <= :START_DATE ) – 100 )
       AS NSIN_GROWTH from “COMPARE_STOCK”.”androidapphana::PRICE_HISTORY” 
  where NSIN = :NSIN1
  and   DATE <= :END_DATE

  union (

     select top 1 ( 100 * DAY_CLOSE / ( select top 1 DAY_CLOSE from “COMPARE_STOCK”.”androidapphana::PRICE_HISTORY” 
  where NSIN = :NSIN2
  and   DATE <= :START_DATE ) – 100 )
       AS NSIN_GROWTH from “COMPARE_STOCK”.”androidapphana::PRICE_HISTORY” 
  where NSIN = :NSIN2
  and   DATE <= :END_DATE
  ) ;
END /********* End Procedure Script ************/
  • Let me comment this SQL Script a little bit.
    • In order to do a division of a field of 2 records of the same table, I am using a subselect to get the result
    • In order to return 2 such results with different selection criteria (NSIN1 / NSIN2), I simply use a union of two such single results.
    • In order to stay near to the notion of a “view” I use subselects/unions in thei CalcView. I could have also used other syntax elements to do it.
    • It is pretty short and easy to read.
  • It should look like this in HANA Studio:

SQLScript.png

  • Finally, right-click on Output Parameter var_out in the right-most pane, click Create… and on the upcoming popup, click the small green + and put:
    • Name = “NSIN_GROWTH”, Data Type = Decimal, Length = 25, Scale = 12
  • Now, click the Output box in the left-most pane of the three panes again, right-click NSIN_GROWTH in the middle pane and click Add Attribute. (As a consequence, you will see it appear in the right-most pane.) Right-click NSIN_GROWTH and click Create Variable. In the popup, activate Multiple Values and click OK.
  • In the Navigator pane on the left, right-click COMPARE_STOCK_CV and click Activate. In the upcoming popup COMPARE_STOCK_CV is already being selected, just click Activate. (In case you see a “Completed with errors” in the Job Log display, double click on the line: you only have to make sure that in the upper table  “Summary Report” there is Success for the activation of your Calculation View. There might still be other errors not important for us…)
The last step thatneeds to be done is to adapt the xsodata service definition:
  • In the Project Explorer tab on the left, double click -> androidapponhana –> StockDataIF.xsodata. Add 1 row so that it looks in total:
service {
  “androidapphana::PRICE_HISTORY” as “History” ;
  “androidapphana::COMPARE_STOCK_CV” as “CalcView”  keys generate local “ID” parameters via entity “AppParams” results property “AppResults” ;
  }
  • In addition to our existing service definition “History” we now have  a service CalcView which needs 4 input parameters. They are input in the HTTP Request like this (which we have done in the App coding):
http://<hana.server.name>:80<HANA_instance_number>/StockDataIF.xsodata/” +
     “AppParams(NSIN1='” + nsin1String + “‘,NSIN2='” + nsin2String +
     “‘,START_DATE=datetime'” + startDateString + “T00:00:00.0000000’,” +
        “END_DATE=datetime'” + endDateString + “T00:00:00.0000000′)/AppResults?$format=json”
  • Also make sure to replace “<hana.server.name>:80<HANA_instance_number>” with your HANA system (for instance “23.20.228.188:8000”)
  • Click the overall Save button.
  • In Project Explorer, right-click androidapponhana –> Team –> Commit.
  • In Project Explorer, right-click androidapponhana –> Team –> Activate.
That is it. Now test the App again (with the dates mentioned above). No surprise, it looks the same as in the frontend approach.

What did we learn?

  • We can achieve the same goals with 2 different techniques, one using frontend computing and therefore getting some more data to the device – which is normally a bad idea (of course acceptable for 4 fields) – the other one using backend computing in HANA.
  • The xsodata service definitions in HANA are pretty lighweight. They offer input and output parameters.
  • SQL Script is a powerful tool to do selection and computing (as well as aggregations, call of business functions, predictive ….some restricted imperative programming) in HANA. You would only return the results to the device.
  • In SQL Script, the logics of what you want to do has to be transformed because of the SQL approach of handling data.
All in all, I wanted to show how easy it is to create your own, tailored, light-weight xsodata service definitions in HANA. To put it as a general statement, I believe this is what you should do in order to achieve best performance for your App.

To report this post you need to login first.

12 Comments

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

  1. Jan Teichmann Post author

    After making the change in the 1st blog to announce that the 2nd blog is out it was waiting for approval. This has been done – finally.

    So, now you should be able to see it again!

    Sorry for that, Jan

    (0) 
  2. Albrecht Gass

    Jan,

    I love the idea of getting in XS and Android programming. I just notices that both the Android SDK and ABAP in Eclipse use the same acronym – ADT.

    Any chance you can make the final source code available for the 2 new cases? Doing the delete/add operations is a bit error prone.

    Albrecht

    (0) 
    1. Jan Teichmann Post author

      Albrecht,

      good idea!

      I added a section “Source Code Upload” and the ziped content. But please keep in mind that you would still have to change IP addresses, users, passwords and add DB content…

      Jan

      (0) 
    1. Jan Teichmann Post author

      Hi,

       

      yes, I was just using the SYSTEM / pw.

      On HANA One I also used the SYSTEM user.
      Does your Security group contain an open TCP port “8000” ? Did you replace the “androidapponhana” with the real project name? Please check again thoroughly!

      Jan

      (0) 
      1. msap sdn

        Hi,

          Thanks for your reply.

          What do you mean by Security group ?

          I’ve used the same project name “androidapponhana”

          the link I should use, is this something like this ?

        http://<host&gt;:80<instance>/androidapponhana/StockDataIF.xsodata/History” where host is “hanacloud” and instance is “00” like mentioned in http://scn.sap.com/docs/DOC-28191

        Regards

        (0) 
        1. Jan Teichmann Post author

          Hi,   

          I mean: AWS Management Console –> Instances –> Security Groups –> (select yours) –> Inbound.

          Project name is OK – I just wanted you to check in HANA Studio and in the browser again. And <host> and <instance> (always 00 on AWS) seem OK as well.

          And it still doesn’t work? You could also try an easier service: <host>:8000/androidapponhana/StockDataIF.xsodata/$metadata

          Jan

          (0) 
          1. msap sdn

            Hi,

            I’m using CloudShare (30 day trial)

              Hana studio is installed, but I don’t think AWS managment console

            Regards

            (0) 
            1. Jan Teichmann Post author

              Hi,

              after getting in touch with you and getting the credentials, I was able to connect my HANA Studio to your HANA server on Cloudshare. It seems as if there were some WiFi / internet connectivity issues. Everything in your HANA server looked fine and the http connection to HANA worked and showed the expected result.

              Jan

              (0) 
  3. Neeraj Gupta

    Hi Jan,

    i was trying to create a sample android application as mentioned in your previous blog while launching the application in android emulator , i am getting following error :-

    unfortunately, Android app_name has stopped.

    Please help on this.

    Regards,

    Neeraj

    (0) 
  4. Lakhan Yadav

    Hello Jan,

    following your nice tutorial on

    how-to-create-an-android-app-on-sap-hana-one-in-7-steps Part 1

    in similar fashion i have got o-data response of table in Web-based Development Workbench in HCP Trail account

    i want to parse it and consume that o-data in android device in the same way you parsed your o-data in JSON format in part 1.

    the manner of o-data generated is different in tutorial of part1 and o-data in WEB ID is different although it give o-data in output

    can you assist me for that  i have attached WEB ID and it response when service.odata file is executed.WebID work bench.png

    WebID.png

    i tried the method as of part1 like

                 try {

                        URL myhana = new URL(

                            “https://s8hanaxs.hanatrial.ondemand.com/p1941467736trial/test/Demo8XS/services.xsodata?$format=json“);

                        URLConnection hanacon;

                       hanacon = myhana.openConnection();

                       hanacon.setReadTimeout(1000);

                       hanacon.setConnectTimeout(1000);

                       String userpass = “DEV_6FP7U6IHUDUEWSM07IXW29C9H” + “:” + “xxx”;

                       String basicAuth = “Basic ” + new String(Base64.encode(userpass.getBytes(), 0));

                       hanacon.setRequestProperty (“Authorization”, basicAuth);

                       BufferedReader in = new BufferedReader(new InputStreamReader(hanacon.getInputStream()));

                       String inputLine;

                       while ((inputLine = in.readLine()) != null) JASONrs += inputLine;

                       in.close();

                   } catch (Exception e) {

                       e.printStackTrace();

                       return “Error”;

                  }

    but it give

    System.err(17209): org.json.JSONException: Value You of type java.lang.String cannot be converted to JSONObject

    /wp-content/uploads/2016/03/device_2016_03_24_173708_915180.png

    i tried  other ways also bit it gives file not found on device

    Regards & Thanks

    Lakhan

    (0) 

Leave a Reply