This Android Application demonstrates
http://odata4j.org/ does not support oData v4
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.skv.alertsapp.MainActivity" >
<ListView
android:id="@+id/lvAlerts"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="14dp" >
</ListView>
</RelativeLayout>
public class AlertsData {
public int id ;
public String alertType;
public String alertSubType;
public String alertMessage;
public String payload;
public String locationName;
}
In this method connect to oData V4 data service, get Alerts entity set and store it in an array of custom data object (AlertData)
In this method AlertsData is filled into ListView adapter object. Every time old data is cleared before new data is added
Since this class is going to use the oData classes copy the odata jar file (the version I built from source)
olingo-client-android-4.0.0-beta-01-SNAPSHOT.jar into project's libs folder and add as External Jar to buildpath
Add following member variables to the class
AlertsData [] alerts;
AlertsAdapter _adapter;
String serviceUri;
ODataClient odata;
Write the helper function to getData from Server
public void getData(String type)
{
// maximum number of top N items we want to display
int sz= 20;
serviceUri = "http://192.168.1.101/Alerts";
//instanciate client object of specific version through factory class
odata = ODataClientFactory.getV4();
//set request data format as JSON
odata.getConfiguration().setDefaultPubFormat(
ODataFormat.JSON);
//use the helper function defined below
final List<ODataEntity> entities =
readEntities(odata,serviceUri,type);
if(entities.size() < sz)
sz =entities.size();
alerts = new AlertsData[ sz];
for( int i=0 ; i < sz; i++ )
{
ODataEntity alert = entities.get(i);
alerts[i] = new AlertsData();
alerts[i].id = Integer.parseInt(
alert.getProperty("id").getValue().toString());
alerts[i].alertMessage = alert.getProperty("alertMessage").getValue().toString();
alerts[i].locationName = alert.getProperty("LocationName").getValue().toString();
alerts[i].alertType = alert.getProperty("AlertType").getValue().toString();
alerts[i].alertSubType = alert.getProperty("AlertSubType").getValue().toString();
alerts[i].payload = alert.getProperty("Payload").getValue().toString();
}
}
List<ODataEntity> readEntities(ODataClient odata, String uri, String esetname) {
//create entityset request with URI and entitySet Name using request factory
final ODataEntitySetRequest<ODataEntitySet> request =
odata.getRetrieveRequestFactory()
.getEntitySetRequest(
odata.newURIBuilder(uri).appendEntitySetSegment(esetname).build()
);
//execute the request , get response
final ODataRetrieveResponse<ODataEntitySet> response = request.execute();
//get entityset and all entities for the entityset
final ODataEntitySet entitySet = response.getBody();
List<ODataEntity> entities = entitySet.getEntities();
return entities;
}
Fill the methods doInBackground and onPostExecute as shown below
@Override
protected Void doInBackground(String... arg0) {
getData(arg0[0]);
return null;
}
@Override
protected void onPostExecute(Void a)
{
if(alerts != null)
{ {
_adapter.clear();
for(int i=0; i <alerts.length; i++)
{
_adapter.add(alerts[i]);
}
_adapter.notifyDataSetChanged();
}
}
Create the layout xml for columns for the ListView row- how to consume oData V4 Service
- Periodically pull the data using timer from external oData Service
- Show data in multi-column list view
- Show different column list on portrait and landscape mode
oData is a industry standard protocol for consuming and producing data API built on HTTP and REST.
Refer http://www.odata.org/ for details of oData.
I am using http://olingo.apache.org/ Apache olingo android client library for odata v4 support
Apache olingo library's support for V4 is still under beta, one need to download the source from Apache olingo Source and build android library using Apache maven
For setting up oData V4 Server application using Microsoft Visual Studio 2013 refer to my blog
Web API 2 Application using EntityFramework 6.0 supporting oData v4
This website is hosted in my PC with website name as Alerts
Pre-requisites
I have setup Android development environment . I am using Eclipse
Version: Juno Service Release 2
Build id: 20130225-0426
Pre-requisites
I have setup Android development environment . I am using Eclipse
Version: Juno Service Release 2
Build id: 20130225-0426
and Android SDK upto date
The Project consists of mainly MainActivity with a ListView to display Alerts list, A Custom Adapter (AlertsAdapter class) to feed data to the ListView, and a DataService class which pulls data from Server using oData protocol and pump it to Adapter class.
MainActivity uses a Timer task to pull data periodically from Server
1. Create Project
Create Empty Android Project with MainActivity extending Activity
Add a ListView to activity_main.xml layout
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.skv.alertsapp.MainActivity" >
<ListView
android:id="@+id/lvAlerts"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="14dp" >
</ListView>
</RelativeLayout>
2. Create Data Object class AlertsData
public class AlertsData {
public int id ;
public String alertType;
public String alertSubType;
public String alertMessage;
public String payload;
public String locationName;
}
3. Create DataService class
Since DataService is going to use networking/Sockets underneath, we need to run it in non-UI thread, hence it is created as Async Task class , so extend this from AsyncTask<> class
Override doInBackground() and onPostExecute() methods
doInBackground
In this method connect to oData V4 data service, get Alerts entity set and store it in an array of custom data object (AlertData)
onPostExecute
In this method AlertsData is filled into ListView adapter object. Every time old data is cleared before new data is added
Since this class is going to use the oData classes copy the odata jar file (the version I built from source)
olingo-client-android-4.0.0-beta-01-SNAPSHOT.jar into project's libs folder and add as External Jar to buildpath
Add following member variables to the class
AlertsData [] alerts;
AlertsAdapter _adapter;
String serviceUri;
ODataClient odata;
public void getData(String type)
{
// maximum number of top N items we want to display
int sz= 20;
serviceUri = "http://192.168.1.101/Alerts";
//instanciate client object of specific version through factory class
odata = ODataClientFactory.getV4();
//set request data format as JSON
odata.getConfiguration().setDefaultPubFormat(
ODataFormat.JSON);
//use the helper function defined below
final List<ODataEntity> entities =
readEntities(odata,serviceUri,type);
if(entities.size() < sz)
sz =entities.size();
alerts = new AlertsData[ sz];
for( int i=0 ; i < sz; i++ )
{
ODataEntity alert = entities.get(i);
alerts[i] = new AlertsData();
alerts[i].id = Integer.parseInt(
alert.getProperty("id").getValue().toString());
alerts[i].alertMessage = alert.getProperty("alertMessage").getValue().toString();
alerts[i].locationName = alert.getProperty("LocationName").getValue().toString();
alerts[i].alertType = alert.getProperty("AlertType").getValue().toString();
alerts[i].alertSubType = alert.getProperty("AlertSubType").getValue().toString();
alerts[i].payload = alert.getProperty("Payload").getValue().toString();
}
}
List<ODataEntity> readEntities(ODataClient odata, String uri, String esetname) {
//create entityset request with URI and entitySet Name using request factory
final ODataEntitySetRequest<ODataEntitySet> request =
odata.getRetrieveRequestFactory()
.getEntitySetRequest(
odata.newURIBuilder(uri).appendEntitySetSegment(esetname).build()
);
//execute the request , get response
final ODataRetrieveResponse<ODataEntitySet> response = request.execute();
//get entityset and all entities for the entityset
final ODataEntitySet entitySet = response.getBody();
List<ODataEntity> entities = entitySet.getEntities();
return entities;
}
Fill the methods doInBackground and onPostExecute as shown below
@Override
protected Void doInBackground(String... arg0) {
getData(arg0[0]);
return null;
}
@Override
protected void onPostExecute(Void a)
{
if(alerts != null)
{ {
_adapter.clear();
for(int i=0; i <alerts.length; i++)
{
_adapter.add(alerts[i]);
}
_adapter.notifyDataSetChanged();
}
}
//constructor is used to pass and remember adapter object
public DataService(AlertsAdapter a)
{
_adapter = a;
}
4. Create Custom DataAdapter class
alerts_listitem_row.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView android:id="@+id/txtId"
android:layout_width="0sp"
android:layout_height="fill_parent"
android:layout_weight ="1"
android:textStyle="italic"
android:textSize="14sp"
android:textColor="#000000"
android:layout_marginTop="5dp"
android:text="@+string/AlertType"
android:layout_marginBottom="5dp" />
<TextView android:id="@+id/txtType"
android:layout_width="0sp"
android:layout_height="fill_parent"
android:layout_weight ="2"
android:textStyle="italic"
android:textSize="14sp"
android:textColor="#000000"
android:layout_marginTop="5dp"
android:text="@+string/AlertType"
android:layout_marginBottom="5dp" />
<TextView android:id="@+id/txtSubType"
android:layout_width="0sp"
android:layout_height="fill_parent"
android:layout_weight ="2"
android:textStyle="italic"
android:textSize="14sp"
android:textColor="#000000"
android:layout_marginTop="5dp"
android:text="@+string/AlertType"
android:layout_marginBottom="5dp" />
<TextView android:id="@+id/txtMessage"
android:layout_width="0sp"
android:layout_height="fill_parent"
android:layout_weight ="5"
android:textStyle="italic"
android:textSize="14sp"
android:textColor="#0000FF"
android:layout_marginTop="5dp"
android:text="@+string/AlertMessage"
android:layout_marginBottom="5dp" />
<TextView android:id="@+id/txtPayload"
android:layout_width="0sp"
android:layout_height="match_parent"
android:layout_weight ="2"
android:textStyle="italic"
android:textSize="14sp"
android:layout_marginTop="5dp"
android:text="@+string/AdditionalInfo"
android:layout_gravity="right"
android:gravity="right"
android:layout_marginBottom="5dp" />
</LinearLayout>
The LinearLayout is set with orientation Horizontal and each column is a TextView
The weights are adjusted according to column width requirement
This layout will be used in AlertsAdapter class by inflating thsi for each row data
Create AlertsAdapter derived from ArrayAdapter<AlertsData>
create a nested class alertHolder to hold textviews for all the columns of ListView row
override getView() to custom display each row
Add helper functions FillHolder() and displayFromHolder() to get TextViews for each of the columns and display values for each row column values
alertHolder FillHolder(View row)
{
alertHolder holder = new alertHolder();
holder.alertId = (TextView)row.findViewById(R.id.txtId);
holder.alertMsg = (TextView)row.findViewById(R.id.txtMessage);
holder.payload = (TextView)row.findViewById(R.id.txtPayload);
holder.alertType = (TextView)row.findViewById(R.id.txtType);
holder.alertSubType = (TextView)row.findViewById(R.id.txSubType);
return holder;
}
//Display from AlertsData object values for each column in corresponding TextView in holder object
void displayFromHolder(alertHolder holder,AlertsData alert)
{
String strId ="";
strId = String.format("%05d",alert.id);
holder.alertId.setText(strId);
holder.alertMsg.setText(alert.alertMessage);
holder.alertType.setText(alert.alertType);
holder.alertSubType.setText(alert.alertSubType);
holder.payload.setText(alert.payload);
}
/* First time for each row TextViews are cached in Holder
* and holder object is saved in Tag of each row
* When getView is called for display, holder is extracted from tag and filled with data
* using DisplayFromHolder
*/
@Override* and holder object is saved in Tag of each row
* When getView is called for display, holder is extracted from tag and filled with data
* using DisplayFromHolder
*/
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
LayoutInflater inflater =null;
if(row == null)
{
inflater = ((Activity)_context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = FillHolder(row);
row.setTag(holder);
}
else
{
holder = (alertHolder)row.getTag();
}
AlertsData alert = getItem(position);
displayFromHolder(holder, alert);
return row;
}
//constructor used to save context and resource id, two members added
Context _context;
int layoutResourceId;
public AlertsAdapter(Context context, int resource) {
super(context, resource);
layoutResourceId = resource;
_context = context;
}
5. Binding Together
Following two member variables are added to class MainActivity and the below code in onCreate() method -
public class MainActivity extends ActionBarActivity {
AlertsAdapter adapter;
DataService dataService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView lv =(ListView)findViewById(R.id.lvAlerts);
adapter = new AlertsAdapter(this,
R.layout.alerts_listitem_row);
lv.setAdapter(adapter);
adapter.setNotifyOnChange(true);
dataService = new DataService(adapter);
dataService.execute("Alerts");
}
...
}
6. Application Configuration Changes
We force the ListView display to landscape orientation by setting in AndroidManifest.xml Activity attribute as shown -
...
<activity
android:name=".MainActivity"
android:screenOrientation="landscape"
...
Since we need to access network, INTERNET Access permission need to be requested in in AndroidManifest.xml
...
<uses-permission android:name="android.permission.INTERNET"/>
...
7. Build & Run the Application
Application was run under these two Devices:
iBall Slide Phablet model 7236 2G dual core cortex A7 1.3 ghz
Android 4.2.2 SDK 17
Xolo X900 Intel Atom duel core 1.6 GHZ
Android 4.0.4 SDK 15
Screen snapshot on Xolo X900
github AlertsAppv1
8. Using Timer to periodically pull data
Create an TimerTask extended class
class myTimerTask extends TimerTask
{
@Override
public void run() {
dataService = new DataService(adapter);
dataService.execute("Alerts");
}
}
{
@Override
public void run() {
dataService = new DataService(adapter);
dataService.execute("Alerts");
}
}
Modify MainActivity class onCreate() method as follows
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView lv =(ListView)findViewById(R.id.lvAlerts);
adapter = new AlertsAdapter(this,
R.layout.alerts_listitem_row);
lv.setAdapter(adapter);
timer = new Timer();
/*
dataService = new DataService(adapter);
dataService.execute("Alerts");
*/
//initial delay of 100 msec, execute once in every 10sec
timer.schedule (new myTimerTask(), 100, 10000);
}
The dataService is created and executed in TimerTask instead of in onCreate()
Please note we need to create each time new instance of DataService, as one instance can execute only once
Build & Run
Application ran on iBall Slide as expected, executing getData every 10 sec
However on Xolo, throws
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
It looks like problem is with older version of android (SDK 15) and seem to work on SDK 17
Anyway ensuring DataService task runs in UI thread, the problem gets solved as follows -
class myTimerTask extends TimerTask
{
@Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
dataService = new DataService(adapter);
dataService.execute("Alerts");
}
});
}
}
You may download this version from AlertsAppV2.zip
Modify the Manifest file as follows -
alerts_listitem_min_row.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<TextView android:id="@+id/txtId"
android:layout_width="0sp"
android:layout_height="fill_parent"
android:layout_weight ="1"
android:textStyle="italic"
android:textSize="14sp"
android:textColor="#000000"
android:layout_marginTop="5dp"
android:text=""
android:layout_marginBottom="5dp"
/>
<TextView android:id="@+id/txtMessage"
android:layout_width="0sp"
android:layout_height="fill_parent"
android:layout_weight ="5"
android:textStyle="italic"
android:textSize="14sp"
android:textColor="#0000FF"
android:layout_marginTop="5dp"
android:text="@+string/AlertMessage"
android:layout_marginBottom="5dp" />
</LinearLayout>
9. Enhancing the application to show different column list on portrait and landscape mode
Need to do following changes- create a new list item row for portrait mode
- create a new AlertsAdapter class to handle data display in ListView
- Override onConfigurationChanged to intercept orientation changes
Modify the Manifest file as follows -
- remove under activity the orientation android:screenOrientation="landscape"
- add android:configChanges="orientation|keyboardHidden|screenSize"
alerts_listitem_min_row.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<TextView android:id="@+id/txtId"
android:layout_width="0sp"
android:layout_height="fill_parent"
android:layout_weight ="1"
android:textStyle="italic"
android:textSize="14sp"
android:textColor="#000000"
android:layout_marginTop="5dp"
android:text=""
android:layout_marginBottom="5dp"
/>
<TextView android:id="@+id/txtMessage"
android:layout_width="0sp"
android:layout_height="fill_parent"
android:layout_weight ="5"
android:textStyle="italic"
android:textSize="14sp"
android:textColor="#0000FF"
android:layout_marginTop="5dp"
android:text="@+string/AlertMessage"
android:layout_marginBottom="5dp" />
</LinearLayout>
Modify AlertsAdapter class qualifying AlertsHolder class as protected
Create a class AlertsAdapterMini extending AlertsAdapter class and override FillHolder and displayFromHolder methods as follows -
public class AlertsAdapterMini extends AlertsAdapter {
public AlertsAdapterMini(Context context, int resource) {
super(context, resource);
}
@Override
alertHolder FillHolder(View row)
{
alertHolder holder = new alertHolder();
holder.alertId = (TextView)
row.findViewById(R.id.txtId);
holder.alertMsg = (TextView)
row.findViewById(R.id.txtMessage);
return holder;
}
@Override
void displayFromHolder(alertHolder holder,
AlertsData alert)
{
String strId ="";
strId = String.format("%05d",alert.id);
holder.alertId.setText(strId);
holder.alertMsg.setText(alert.alertMessage);
}
}
override onConfigurationChanged to intercept orientation change in MainActivity class -
@Override
public void onConfigurationChanged(
Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ListView lv =(ListView)findViewById(R.id.lvAlerts);
if(timer != null){
timer.cancel();
timer.purge();
}
timer = new Timer();
lv.setAdapter(null);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
adapter = new AlertsAdapter( this, R.layout.alerts_listitem_row);
} else
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
adapter = new AlertsAdapterMini( this, R.layout.alerts_listitem_min_row );
}
lv.setAdapter(adapter);
timer.schedule (new myTimerTask(), 100, 10000);
}
Next Modify the onCreate method of MainActivity class
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView lv =(ListView)findViewById(R.id.lvAlerts);
if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
adapter = new AlertsAdapter(this,R.layout.alerts_listitem_min_row);
else
adapter = new AlertsAdapter(this,R.layout.alerts_listitem_row);
lv.setAdapter(adapter);
timer = new Timer();
//delay 100 msec, every 10sec
timer.schedule (new myTimerTask(), 100, 10000);
}
Build and run the project
Screenshot when Landscape mode
Screenshot when Portrait mode
You may download this version from AlertsAppV3.zip


No comments:
Post a Comment