Create the following three Java client-side classes in
your Android project:
TraderDesktop: Represents the main class that
implements android.app.Activity. This class contains
the onCreate method, which is the entry point for
the Android application (the new project wizard creates this class).
Most of the application logic in this class setups the user interface
and does not contain application logic related to Data Services.
This class belongs to the flex.samples.marketdata.clients.android package.
TraderDesktopModel: Represents the class that makes
a connection to Data Services. This class subscribes to a Message
service destination by using a MultiTopicConsumer instance.
The destination to which this application subscribes exists as part
of the Trader Desktop sample application that is available with
Data Services.
Stock: Represents a stock that contains data members
that store information such as high value, low value, open value,
and so on.
The following illustration shows the Package Explorer view of
the Android project that contains these classes.
Note: In addition to creating the Java files, be sure to replace
main.xml with content located in this section and create the traderdesktop.properties
file. The content for the traderdesktop.properties file is specified
later in this section.
Creating the TraderDesktop class
The TraderDesktop class
represents the main class for the Android application that implements android.app.Activity.
This class streams updates from Data Services. In addition, this
class defines the user interface that is displayed in the Android
screen. In this application, the user interface displays stock data,
as shown in the illustration displayed at the beginning of this
section.
To set up the user interface, define an android.widget.TableLayout instance
that displays data in a row and column format. The main.xml file
located in the res/layout folder in your project defines the columns.
The following XML represents the main.xml that is part of the Android
trader desktop. (Notice that the main.xml file is displayed in the
previous illustration.)
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mainTable"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="*">
<TableRow >
<TextView
android:layout_column="0"
android:text="Symbol"
android:textStyle="bold" android:textSize="15sp"/>
<TextView
android:layout_column="1"
android:text="Open"
android:textStyle="bold" android:textSize="15sp"/>
<TextView
android:layout_column="2"
android:text="Last"
android:textStyle="bold" android:textSize="15sp"/>
<TextView
android:layout_column="3"
android:text="Change"
android:textStyle="bold" android:textSize="15sp"/>
<TextView
android:layout_column="4"
android:text="High"
android:textStyle="bold" android:textSize="15sp"/>
<TextView
android:layout_column="5"
android:text="Low"
android:textStyle="bold" android:textSize="15sp"/>
</TableRow>
</TableLayout>
Notice that the identifier value
is mainTable as defined by the android:id tag.
The following code example represents the onCreate method
that is located in the TraderDesktop class.
@Override public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Initialize the model and the layout
traderDesktopModel = new TraderDesktopModel(this);
tableLayout = (TableLayout)findViewById(R.id.mainTable);
// Start the stock feed.
traderDesktopModel.initializeWatchList();
}
The findViewById method returns
the android.widget.TableLayout instance that corresponds
to the view defined in the main.xml file. Notice that the return
value is cast to android.widget.TableLayout. The R.id.mainTable parameter
value that is passed to the findViewById method
references the main.xml file.
The TraderDesktop class
contains a method named addRow that adds rows to
the TableLayout instance. Each row displays stock
data. An android.widget.TableRow instance that
represents a row is created in the addRow method.
The android.widget.TableRow instance
is added to the android.widget.TableLayout instance
by invoking the android.widget.TableLayout instance’s addView method
and passing the android.widget.TableRow instance.
The
following code example represents the addRow method
located in the TraderDesktop class.
private void addRow(Stock stock)
{
TableRow tableRow = new TableRow(this);
tableLayout.addView(tableRow);
symbolRows.put(stock.getSymbol(), tableRow);
TextView textView0 = new TextView(this);
textView0.setText(stock.getSymbol());
tableRow.addView(textView0);
TextView textView1 = new TextView(this);
textView1.setText(String.valueOf(stock.getOpen()));
tableRow.addView(textView1);
TextView textView2 = new TextView(this);
textView2.setText(String.valueOf(stock.getLast()));
tableRow.addView(textView2);
TextView textView3 = new TextView(this);
double change = stock.getChange();
textView3.setText(String.valueOf(stock.getChange()));
setColor(textView3, change);
tableRow.addView(textView3);
TextView textView4 = new TextView(this);
textView4.setText(String.valueOf(stock.getHigh()));
tableRow.addView(textView4);
TextView textView5 = new TextView(this);
textView5.setText(String.valueOf(stock.getLow()));
tableRow.addView(textView5);
}
An inner class named HandlerRunnable is
defined that implements java.lang.Runnable. This
class controls when stocks are updated. The following code example
represents the entire TraderDesktop.java file, including the HandlerRunnable class.
Notice that the TraderDesktop class is located
in a package named flex.samples.marketdata.clients.android.
package flex.samples.marketdata.clients.android;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import flex.client.ApplicationFramework;
import flex.client.ApplicationFramework.ApplicationFocusState;
import flex.samples.marketdata.Stock;
/**
* Android version of the trader desktop that uses LCDS Java client library APIs
* to get streaming updates from a LCDS messaging destination on multiple subtopics.
* See the associated model for LCDS data access related code.
*/
public class TraderDesktop extends Activity
{
private static final int COLOR_RED = 0xFFFF0000;
private static final int COLOR_GREEN = 0xFF00FF00;
private static final int COLOR_WHITE = 0xFFFFFFFF;
// Handler for UI update callbacks to the UI thread.
private final Handler handler = new Handler();
private final HandlerRunnable handlerRunnable = new HandlerRunnable();
// Map of stock symbols and TableRows used in UI updates.
private final Map<String, TableRow> symbolRows = new ConcurrentHashMap<String, TableRow>();
private TableLayout tableLayout;
private TraderDesktopModel traderDesktopModel;
@Override public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Initialize the model and the layout.
traderDesktopModel = new TraderDesktopModel(this);
tableLayout = (TableLayout)findViewById(R.id.mainTable);
// Start the stock feed.
traderDesktopModel.initializeWatchList();
for (String symbol : traderDesktopModel.watchList)
{
TableRow tableRow = new TableRow(this);
tableLayout.addView(tableRow);
symbolRows.put(symbol, tableRow);
TextView textView0 = new TextView(this);
textView0.setText(symbol);
tableRow.addView(textView0);
TextView textView1 = new TextView(this);
textView1.setText("0.0");
tableRow.addView(textView1);
TextView textView2 = new TextView(this);
textView2.setText("0.0");
tableRow.addView(textView2);
TextView textView3 = new TextView(this);
double change = 0;
textView3.setText("0.0");
setColor(textView3, change);
tableRow.addView(textView3);
TextView textView4 = new TextView(this);
textView4.setText("0.0");
tableRow.addView(textView4);
TextView textView5 = new TextView(this);
textView5.setText("0.0");
tableRow.addView(textView5);
}
}
/**
* Used by the model to add or update stocks. This method uses the handler
* to post updates to the UI thread.
*
* @param stock The stock.
*/
public void addOrUpdateStock(Stock stock)
{
handlerRunnable.addUpdate(stock);
handler.post(handlerRunnable);
}
private void addOrUpdateRow(Stock stock)
{
TableRow tableRow = symbolRows.get(stock.getSymbol());
if (tableRow != null)
updateRow(tableRow, stock);
else
addRow(stock);
}
private void addRow(Stock stock)
{
TableRow tableRow = new TableRow(this);
tableLayout.addView(tableRow);
symbolRows.put(stock.getSymbol(), tableRow);
TextView textView0 = new TextView(this);
textView0.setText(stock.getSymbol());
tableRow.addView(textView0);
TextView textView1 = new TextView(this);
textView1.setText(String.valueOf(stock.getOpen()));
tableRow.addView(textView1);
TextView textView2 = new TextView(this);
textView2.setText(String.valueOf(stock.getLast()));
tableRow.addView(textView2);
TextView textView3 = new TextView(this);
double change = stock.getChange();
textView3.setText(String.valueOf(stock.getChange()));
setColor(textView3, change);
tableRow.addView(textView3);
TextView textView4 = new TextView(this);
textView4.setText(String.valueOf(stock.getHigh()));
tableRow.addView(textView4);
TextView textView5 = new TextView(this);
textView5.setText(String.valueOf(stock.getLow()));
tableRow.addView(textView5);
}
private void setColor(TextView textView, double change)
{
if (change < 0)
textView.setTextColor(COLOR_RED);
else if (change > 0)
textView.setTextColor(COLOR_GREEN);
else
textView.setTextColor(COLOR_WHITE);
}
private void updateRow(TableRow tableRow, Stock stock)
{
TextView textView0 = (TextView)tableRow.getChildAt(0);
textView0.setText(stock.getSymbol());
TextView textView1 = (TextView)tableRow.getChildAt(1);
textView1.setText(String.valueOf(stock.getOpen()));
TextView textView2 = (TextView)tableRow.getChildAt(2);
textView2.setText(String.valueOf(stock.getLast()));
TextView textView3 = (TextView)tableRow.getChildAt(3);
double change = stock.getChange();
textView3.setText(String.valueOf(change));
setColor(textView3, change);
TextView textView4 = (TextView)tableRow.getChildAt(4);
textView4.setText(String.valueOf(stock.getHigh()));
TextView textView5 = (TextView)tableRow.getChildAt(5);
textView5.setText(String.valueOf(stock.getLow()));
}
/**
* Inner class used to keep track of stock updates that haven't been applied
* to the UI yet.
*/
class HandlerRunnable implements Runnable
{
private List<Stock> updates = new CopyOnWriteArrayList<Stock>();
@Override public void run()
{
if (updates.isEmpty())
return;
for (Stock stock : updates)
addOrUpdateRow(stock);
updates.clear();
}
void addUpdate(Stock stock)
{
updates.add(stock);
}
}
@Override protected void onPause()
{
ApplicationFramework.notifyApplicationFocusChange(ApplicationFocusState.BACKGROUND);
super.onPause();
}
@Override protected void onResume()
{
super.onResume();
ApplicationFramework.notifyApplicationFocusChange(ApplicationFocusState.FOREGROUND);
if (C2DMHandler.deviceId == null)
{
Intent intent = new Intent("com.google.android.c2dm.intent.REGISTER");
intent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0));
intent.putExtra("sender", C2DMHandler.SENDER_ID);
startService(intent);
}
}
}
Creating the TraderDesktopModel class
The TraderDesktopModel class
makes a connection to Data Services. Because the Android client
application tracks multiple stocks, a MultiTopicConsumer instance
is used. Using a MultiTopicConsumer instance, the
client application receives messages from different subtopics that are
located in the destination. As information is obtained about a stock,
it is updated in the client application.
The TraderDesktopModel class
contains two methods:
initializeWatchList:
Subscribes to a Message Service destination by using a MultiTopicConsumer instance.
The name of the destination is defined in the traderdesktop.properties
file.
loadProperties: Loads properties from the traderdesktop.properties
file. Property values are stored in data members that belong to
the TraderDesktopModel class.
The
traderdesktop.properties file contains properties that the client
application requires to subscribe to the destination. This file
defines a value for the destination identifier value, the channel
identifier value, and the URL of the server hosting Data Services.
The traderdesktop.properties file also contains a property named WATCH_LIST that
contains a comma delimited list of stock symbols. The client application
tracks the stocks that are specified in this list.
The following
code represents the traderdesktop.properties file.
DESTINATION_ID=market-data-feed
CHANNEL_ID=my-polling-amf
CHANNEL_URL=http://<ServerName>:<Port>/dataservices/messagebroker/amfpolling
WATCH_LIST=ADBE, BA, COKE, GE, IBM, JBLU, MOT, MCD, XOM, SAP
Note: Place the traderdesktop.properties
file in the flex.samples.marketdata.clients.android package. Also,
replace ServerName and Port with the IP address of the server hosting
the Experience Server. Also note that LocalHost cannot be used with
an Android application.
The initializeWatchList method
contains application logic that subscribes to the destination specified
in the traderdesktop.properties file. In this example application,
the destination is named market-data-feed. Within this
method, a MultiTopicConsumer instance named consumer is
created by using its constructor and passing a string value that
specifies the destination identifier value.
MultiTopicConsumer consumer = new MultiTopicConsumer(destinationId);
Next,
a Channel instance is created by using its constructor
and passing the channel identifier value, channel URL value. Both
of these values are retrieved from the traderdesktop.properties
file. In addition, the third and forth paramater values are HttpMode.POLLING and Encoding.AMF.
A ChannelSet instance
is created by using the ChannelSet constructor
and passing the Channel instance. The MultiTopicConsumer object’s setChannelSet method
is invoked and the ChannelSet instance is passed. This
method defines the channel that the MultiTopicConsumer object
uses to subscribe to the destination.
The MultiTopicConsumer object’s addMessageListener method
is invoked to define the message listening events. For example,
an event handler method is defined that is invoked when a message
is received from the destination. In this example, the method is
named MessageRecieved. The following code example
shows the MultiTopicConsumer object’s addMessageListener method
defining the event handler methods.
consumer.addMessageListener(new MessageListener()
{
public void messageAcknowledged(MessageAckEvent ackEvent)
{
System.out.println(ackEvent);
}
public void messageFaulted(MessageFaultEvent faultEvent)
{
ErrorMessage errorMessage = faultEvent.getError();
System.err.println(errorMessage.faultString);
}
public void messageReceived(MessageEvent messageEvent)
{
Message message = messageEvent.getMessage();
Stock changedStock = (Stock)message.getBody();
traderDesktop.addOrUpdateStock(changedStock);
}
});
The following Java code represents the TraderDesktopModel class.
package flex.samples.marketdata.clients.andr oid;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.Properties;
import flex.client.channels.Channel;
import flex.client.channels.Channel.Encoding;
import flex.client.channels.Channel.HttpMode;
import flex.client.channels.ChannelSet;
import flex.client.messaging.MultiTopicConsumer;
import flex.client.messaging.events.MessageAckEvent;
import flex.client.messaging.events.MessageEvent;
import flex.client.messaging.events.MessageFaultEvent;
import flex.client.messaging.events.MessageListener;
import flex.messaging.messages.ErrorMessage;
import flex.messaging.messages.Message;
import flex.samples.marketdata.Stock;
/**
* Table model for the trader desktop that reads configuration values from the properties
* file and handles the data connectivity with the Data Services destination in initializeWatchList.
*/
public class TraderDesktopModel
{
private final TraderDesktop traderDesktop;
private String destinationId;
private String channelId;
private String channelUrl;
private String[] watchList;
/**
* Construct the model with the supplied desktop
* @param traderDesktop The associated trader desktop
*/
TraderDesktopModel(TraderDesktop traderDesktop)
{
this.traderDesktop = traderDesktop;
loadProperties();
}
/**
* Main method that handles data connectivity using a MultiTopicConsumer instance
*/
void initializeWatchList()
{
MultiTopicConsumer consumer = new MultiTopicConsumer(destinationId);
Channel channel = null;
try
{
channel = new Channel(channelId, channelUrl, HttpMode.POLLING, Encoding.AMF);
}
catch (MalformedURLException e1)
{
e1.printStackTrace();
}
ChannelSet channelSet = new ChannelSet(channel);
consumer.setChannelSet(channelSet);
consumer.addMessageListener(new MessageListener()
{
public void messageAcknowledged(MessageAckEvent ackEvent)
{
System.out.println(ackEvent);
}
public void messageFaulted(MessageFaultEvent faultEvent)
{
ErrorMessage errorMessage = faultEvent.getError();
System.err.println(errorMessage.faultString);
}
public void messageReceived(MessageEvent messageEvent)
{
Message message = messageEvent.getMessage();
Stock changedStock = (Stock)message.getBody();
traderDesktop.addOrUpdateStock(changedStock);
}
});
for (String symbol : watchList)
consumer.addSubscription(symbol, null, 0);
consumer.subscribe();
boolean ans = consumer.isSubscribed();
System.out.println(ans);
}
//Loads properties from the traderdesktop.properties file
private void loadProperties()
{
Properties properties = new Properties();
try
{
InputStream inStream = getClass().getClassLoader().getResourceAsStream("flex/samples/marketdata/clients/android/traderdesktop.properties");
properties.load(inStream);
destinationId = properties.getProperty("DESTINATION_ID");
channelId = properties.getProperty("CHANNEL_ID");
channelUrl = properties.getProperty("CHANNEL_URL");
String watchList = properties.getProperty("WATCH_LIST");
if (watchList != null)
{
this.watchList = watchList.split(",");
for (int i = 0; i < this.watchList.length; i++)
this.watchList[i] = this.watchList[i].trim();
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Creating the Stock class
The Stock class
contains data members that store stock information such as high
value, low value, open value, and so on. This class contains a method
named simulateChange that modifies the value of
the stock price by using random values. Notice that this class belongs
to a package named flex.samples.marketdata.
Note: Modifying
stock data by using random values is used only in the context of
a sample application. A real world application typically uses a
third-party financial content provider to retrieve stock data. For
information about using Data Services with a third party financial
content provider, see Creating live stock quote sample applications using the Message service.
The
following Java code represents the Stock class.
package flex.samples.marketdata;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.Random;
public class Stock implements Comparable<Stock>
{
private String symbol;
private String name;
private double open;
private double last;
private double change;
private double low;
private double high;
private Date date;
private final Random random = new Random();
private final DecimalFormat formatter = new DecimalFormat("####.##");
public Stock()
{
// No-op.
}
public Stock(String symbol, String name, double open, double last,
double change, double low, double high)
{
this.symbol = symbol;
this.name = name;
this.open = open;
this.last = last;
this.change = change;
this.low = low;
this.high = high;
date = new Date();
}
public String getSymbol()
{
return symbol;
}
public void setSymbol(String symbol)
{
this.symbol = symbol;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public double getOpen()
{
return Double.valueOf(formatter.format(open));
}
public void setOpen(double open)
{
this.open = open;
}
public double getLast()
{
return Double.valueOf(formatter.format(last));
}
public void setLast(double last)
{
this.last = last;
}
public double getChange()
{
return Double.valueOf(formatter.format(change));
}
public void setChange(double change)
{
this.change = change;
}
public double getLow()
{
return Double.valueOf(formatter.format(low));
}
public void setLow(double low)
{
this.low = low;
}
public double getHigh()
{
return Double.valueOf(formatter.format(high));
}
public void setHigh(double high)
{
this.high = high;
}
public Date getDate()
{
return date;
}
public void setDate(Date date)
{
this.date = date;
}
@Override public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((symbol == null) ? 0 : symbol.hashCode());
return result;
}
@Override public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Stock other = (Stock)obj;
if (symbol == null)
{
if (other.symbol != null)
return false;
}
else if (!symbol.equals(other.symbol))
return false;
return true;
}
void simulateChange()
{
double maxPerChange = 0.1; // Limit changes to less than 10%.
double maxChange = last * (random.nextDouble() * maxPerChange);
double currentChange = random.nextDouble() * maxChange;
boolean increasing = random.nextBoolean();
change = increasing? currentChange : currentChange * -1;
last = increasing? last + currentChange : last - currentChange;
if (last > high)
high = last;
else if (last < low)
low = last;
date = new Date();
}
public int compareTo(Stock other)
{
return symbol.compareTo(other.symbol);
}
}