Quantcast
Channel: SCN : Blog List - SAP HANA Developer Center
Viewing all articles
Browse latest Browse all 676

Extend an Android app on SAP HANA One in 20 minutes

$
0
0

Introduction

Sorry, for waiting so long to get this 2nd blog out - there so many thing 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.
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

    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.

    What did we learn?

    After doing this step-by-step I want to make some remarks:
    • 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
    privateclass 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.
    • Finally, right-click on Output Parameter var_outin 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 have learnt the following:
    • 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.

    Viewing all articles
    Browse latest Browse all 676

    Trending Articles



    <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>