Taste of Android :: Part-7


Bhaskar S 06/02/2013


Android Application Development - VI

When an Android application is launched, the Android system starts a new Linux process with a single thread of execution. The main thread of execution if often referred to as the main thread or the UI thread and is responsible for rendering all the widgets in an Activity as well as handling events due to user interactions.

What happens when we have to update the user interface by fetching data from the database or the network which can take some time ? If we used the UI thread, it will result in a sluggish performance which will lead to poor user experience.

Hmmm. We should be able to create a new background thread to help out here - can't we ???

Well, it turns out that we have to honor the following two golden rules of an user interface (Activity) in Android:

Enter the Android framework class android.os.Handler. A Handler allows one to send and receive messages. The messages are instances of the Android framework class android.os.Message. A Handler is associated with the main UI thread and is responsible for updating the user interface in response to a Message from another thread. When a new instance of Handler is created, it is associated with the thread creating the instance. Typically, an instance of Handler is created in the Activity so that its associated with the main UI thread. This way we have honored the golden rules of the user interface.

We will now demonstrate this concept of using Threads with Messages and Handlers by creating a new Android application named DroidStockTicker.

The application will contain an Activity that will display a list of stock symbols along with thier current prices and value changes. We will use a ListView to display the stock ticker for each symbol in a separate row. Each row will be divided into four columns to display symbol, current price, price change, and an image indicating the change direction (green up arrow, red down arrow, etc). In this application we will simulate the stock prices locally rather than making any network calls to fetch the real prices.

Without further ado, lets get started. We will not go step-by-step to show the various screens since we already did that in Part-2 for the DroidTipCalculator application.

The contents of the dimens.xml file to look like the one shown in the listing 7.1 below:

Listing-7.1
<?xml version="1.0" encoding="utf-8"?>
<resources>

<dimen name="cell_width">0dp</dimen>
<dimen name="list_padding">5dp</dimen>

</resources>

The contents of the strings.xml file to look like the one shown in the listing 7.2 below:

Listing-7.2
<?xml version="1.0" encoding="utf-8"?>
<resources>

<!-- Text references -->

<string name="app_name">DroidStockTicker</string>
<string name="action_settings">Settings</string>

<string name="tick_image">Tick Image</string>

<!-- Array references -->

<string-array name="stocks">
<item>AAPL</item>
<item>AMZN</item>
<item>CSCO</item>
<item>GOOG</item>
<item>IBM</item>
<item>INTC</item>
<item>LNKD</item>
<item>MSFT</item>
<item>NFLX</item>
<item>ORCL</item>
</string-array>

<string-array name="opens">
<item>450</item>
<item>267</item>
<item>24</item>
<item>871</item>
<item>209</item>
<item>24</item>
<item>169</item>
<item>35</item>
<item>223</item>
<item>34</item>
</string-array>

</resources>

From the listing 7.2, we can see that we have defined two arrays - one for the stock symbols and the other for the opening price of the stock.

We will have two layout definitions files - one for the main stock ticker Activity and the other is for each of the row in the ListView. The layout file for the main stock ticker Activity is named activity_stock_ticker.xml while the layout file for the list row is named list_item_row.xml.

The contents of the activity_stock_ticker.xml layout file will look like the one shown in the listing 7.3 below:

Listing-7.3
<LinearLayout 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:orientation="vertical"
tools:context=".StockTickerActivity" >

<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

</LinearLayout>

Previewing the activity_stock_ticker.xml layout file in the Graphical Mode in Eclipse will look like the one shown in the figure 7.1 below:

Preview Activity Stock Ticker Image
Figure-7.1

The contents of the list_item_row.xml layout file will look like the one shown in the listing 7.4 below:

Listing-7.4
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >

<TableRow>
<TextView
android:id="@+id/symbol"
android:layout_width="@dimen/cell_width"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="@dimen/list_padding"
android:text=""
/>

<TextView
android:id="@+id/price"
android:layout_width="@dimen/cell_width"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="@dimen/list_padding"
android:text=""
/>

<TextView
android:id="@+id/change"
android:layout_width="@dimen/cell_width"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="@dimen/list_padding"
android:text=""
/>

<ImageView
android:id="@+id/tick"
android:contentDescription="@string/tick_image"
android:layout_width="@dimen/cell_width"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="@dimen/list_padding"
/>
</TableRow>

</TableLayout>

From the listing 7.4 above, the things of interest would be the following:

We want each row in the list to be divided into four equal sized columns. To achieve this, we need to set the attribute android:layout_width to 0dp and the attribute android:layout_weight to 1.

We will use Java Enum to indicate the direction of the change (up, flat, or down). This is encapsulated in the Java class TickSignal as shown in the listing 7.5 below:

Listing-7.5
package com.polarsparc.android.droidstockticker;

public enum TickSignal {
UP, FLAT, DOWN;
}

We will need an in-memory representation of the stock ticker for each stock symbol that will encapsulate the stock ticker attributes such as the symbol, the open price, the change, and the change direction. This is encapsulated in the Java class StockTicker as shown in the listing 7.6 below:

Listing-7.6
package com.polarsparc.android.droidstockticker;

public class StockTicker {
private String symbol;
private float open;
private float change;

public String getSymbol() {
return symbol;
}

public void setSymbol(String symbol) {
this.symbol = symbol;
}

public float getPrice() {
return open+change;
}

public void setOpen(float open) {
this.open = open;
}

public float getChange() {
return change;
}

public void setChange(float change) {
this.change = change;
}

public TickSignal getTickSignal() {
if (change > 0.0f) {
return TickSignal.UP;
}
else if (change < 0.0f) {
return TickSignal.DOWN;
}
return TickSignal.FLAT;
}
}

As indicated earlier, we are using a ListView to display the stock ticker for the list of stock symbols. The source of data for our ListView will be a list of StockTicker objects in memory. So, how do we connect the list of objects in memory to the ListView ???

Enter the Android framework class Adapter. An Adapter is the bridge between the ListView widget and the underlying data set. It is responsible for taking each instance of StockTicker objects in memory and displaying it as a row in the ListView.

The Adapter class is an interface. The Android framework provides some concrete implementations like the ArrayAdapter class. The ArrayAdapter class is useful for displaying a list (or array) of simple text string items in a ListView. In our case, we want to display four equally spaced columns with text and image. For this we need a customized version of ArrayAdapter. This is encapsulated in the Java class TickerArrayAdapter as shown in the listing 7.7 below:

Listing-7.7
package com.polarsparc.android.droidstockticker;

import java.util.List;

import android.content.Context;
import android.widget.ArrayAdapter;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.widget.TextView;
import android.widget.ImageView;
import android.graphics.Color;

public class TickerArrayAdapter extends ArrayAdapter<StockTicker> {
private List<StockTicker> items;

public TickerArrayAdapter(Context context, List<StockTicker> items) {
super(context, R.layout.list_item_row, items);
this.items = items;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);

convertView = inflater.inflate(R.layout.list_item_row, parent, false);
}

// Symbol
TextView symbol = (TextView)convertView.findViewById(R.id.symbol);
symbol.setText(items.get(position).getSymbol());
symbol.setTextColor(Color.BLUE);

// Price
TextView price = (TextView)convertView.findViewById(R.id.price);
price.setText(String.format("%.02f", items.get(position).getPrice()));
price.setTextColor(Color.BLACK);

// Change
TextView change = (TextView)convertView.findViewById(R.id.change);
change.setText(String.format("%.02f", items.get(position).getChange()));

// Tick Image
ImageView tick = (ImageView)convertView.findViewById(R.id.tick);
switch (items.get(position).getTickSignal()) {
case UP:
tick.setImageResource(R.drawable.up_tick);
change.setTextColor(Color.GREEN);
break;
case FLAT:
tick.setImageResource(R.drawable.flat_tick);
change.setTextColor(Color.GRAY);
break;
case DOWN:
tick.setImageResource(R.drawable.down_tick);
change.setTextColor(Color.RED);
break;
}

return convertView;
}
}

From the listing 7.7 above, the things of interest would be the following:

Finally, we have the java source file corresponding to the main stock ticker Activity called StockTickerActivity.java.

The contents of the java source file StockTickerActivity.java will look like the one shown in the listing 7.8 below:

Listing-7.8
package com.polarsparc.android.droidstockticker;

import java.util.List;
import java.util.ArrayList;
import java.util.Random;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.widget.ListView;
import android.widget.ArrayAdapter;
import android.util.Log;

public class StockTickerActivity extends Activity {
private static String TAG = "StockTickerActivity";

private List<StockTicker> items = new ArrayList<StockTicker>();

private Handler handler;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stock_ticker);

String[] stocks = getResources().getStringArray(R.array.stocks);
String[] opens = getResources().getStringArray(R.array.opens);
if (stocks != null) {
for (int i = 0; i < stocks.length; i++) {
StockTicker st = new StockTicker();
st.setSymbol(stocks[i]);
st.setOpen(Float.valueOf(opens[i]));
st.setChange(0.0f);

items.add(st);
}
}

final ArrayAdapter<StockTicker> adapter = new TickerArrayAdapter(this, items);

final ListView list = (ListView) findViewById(R.id.list);
list.setAdapter(adapter);

handler = new Handler() {
@Override
public void handleMessage(Message msg) {
adapter.notifyDataSetChanged();
}
};
}

@Override
protected void onStart() {
super.onStart();

new Thread() {
@Override
public void run() {
Random rand = new Random();

for (;;) {
try {
// Sleep for 10 seconds
Thread.sleep(10000);

for (int i = 0; i < items.size(); i++) {
float change = rand.nextFloat() * 10;
if (rand.nextInt(100) % 2 == 1) {
change = change * -1.0f;
}
StockTicker st = items.get(i);
st.setChange(change);
}

handler.sendEmptyMessage(0);
}
catch (Throwable t) {
Log.e(TAG, "UpdateThread", t);
}`
}
}
}.start();
}

}

From the code Listing 7.8 above, the following are some of the interesting details:

We are now ready to test our DroidStockTicker application on the virtual Android device we created in Part-1. We will create a Run Configuration for DroidStockTicker as we did in Part-2 for DroidTipCalculator.

Once the run configuration for DroidStockTicker is ready, we will Run the application and the application will come to life as shown in the following figure 7.2 below:

Stock Ticker App Image
Figure-7.2

Wait about 10 seconds and the device screen will be refreshed as shown in the following figure 7.3 below:

Stock Ticker App Image
Figure-7.3

References

Taste of Android :: Part-1

Taste of Android :: Part-2

Taste of Android :: Part-3

Taste of Android :: Part-4

Taste of Android :: Part-5

Taste of Android :: Part-6