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.
- Remarks:
- 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:
- 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)
- 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
You should have gone through http://scn.sap.com/blogs/jan_t/2013/02/16/how-to-create-an-android-app-on-sap-hana-one-in-7-steps.
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>
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" >
<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"
>
</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>
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>
<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>
<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 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 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 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;
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
- odata4j-core-0.7.0.jar e. g. from http://repo1.maven.org/maven2/org/odata4j/odata4j-examples/0.7.0/odata4j-examples-0.7.0-sources.jar
- httpmime-4.2.3.jar e. g. from http://repo1.maven.org/maven2/org/apache/httpcomponents/httpmime/4.2.3/httpmime-4.2.3-sources.jar
- apache-mime4j.jar e. g. from http://www.java2s.com/Code/JarDownload/apache-mime4j/apache-mime4j.jar.zip
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);
....
- deleteShowDialogAsyncTask 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());
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 ));
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 {
String JASONrs = "You will get there!"; try {
URL myhana = new URL(
"http://:80/androidapphana/StockDataIF.xsodata/History?$format=json");
"http://:80/androidapphana/StockDataIF.xsodata/History?$format=json");
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);
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;
String inputLine;
while ((inputLine = in.readLine()) != null)
JASONrs += inputLine;
in.close();
} catch (Exception e) {
e.printStackTrace();
return "Error";
}
return JASONrs;
}
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();
// 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 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>"));
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();
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();
} 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();
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);
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");
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();
}
if (gvMain.getAdapter() == null) {
adapter = new ArrayAdapter<String>(mContext, android.R.layout.simple_list_item_1, performance);
gvMain.setAdapter(adapter);
} else
adapter.notifyDataSetChanged();
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:
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
httpClient.getCredentialsProvider().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("<HANA user name>", "<your pw>"));
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);
while ((inputLine = in.readLine()) != null)
JASONrs += inputLine;
in.close();
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
) ;
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 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>"))
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();
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();
} 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(
URL myhana = new URL(
"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);
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);
String basicAuth = "Basic " + new String(Base64.encode(userpass.getBytes(), 0));
hanacon.setRequestProperty ("Authorization", basicAuth);
BufferedReader in = new BufferedReader(new InputStreamReader(
hanacon.getInputStream()));
String inputLine;
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);
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"));
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());
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 (
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.