Revision History
Major revision on 13th September, 2012 - Add Stock Chart (new feature) from Stock Detail Activity
Introduction
Welcome to COINS Mobile - All Exchange Market Watch
"COINS Mobile" allows you to watch Stock Market Data. It illustrates proper application architecture layering and
uses a common code base for the Business Layer and Data Layer. It then separates out the User Interface and Application Layer into the appropriate device-applications.
This article discusses the power of C# to build a Professional Application for Android. The purpose of writing this article is to
understand what you can do with C# using Visual Studio 2010 similar to how you would develop a Windows Application in C#. All credit goes to Xamarin to
provide such a beautiful platform for .NET developers to build application in Android.
Being a .NET developer (even though I started building Android Applications a few years back), I started using Java/Davlink using
Eclipse Java. With a little bit effort, within a couple of weeks I built a sample application in Java, but I faced few problems in consuming web methods (.NET
Web Services). I had to use Android ksoap (lightweight SOAP client library for the Android platform) which is not very straightforward.
Moreover there are still learning curves to understand and get acquainted with the Java language and Eclipse IDE
(specially for .NET developers). So, I started evaluating Xamarin Mono using C# as a base language for Android.
This is not intended to be a comprehensive discussion of each feature but I will try my level best to break down the whole
application and elaborate, step by step, how to build a professional Android Application in C#. I am sure you'll be able to leverage your existing knowledge of
C# and Visual Studio to quickly build a Professional Android Application.
Background
This application is based on "Stock Market Watch" - Pulling Real Time Stock Quotes from most of the Exchange. "COINSMobile" is targeted to the millions
of Stock Traders in the world who desperately need a system available in their hands to watch their stock details (and in the form of a beautiful Android app no less).
I named it "COINS". I will use the term "COINS" in several areas in the article description which will actually
refer the Application itself.
What COIN Does
It pulls real time stock data through the web service and displays the details of the stock (a multitude of stock data can be viewed in a single screen).
It also provides a further drill-down, detailed view of each stock and chart analysis. The user can create their own custom stock script and view their added stock details.
This Article Covers
- Using
C# and Visual Studio to Develop an Android Application
- Understanding
Activities and Layouts
- Connect
to Web Service
- Using
SQLite Database for Local Data Storage
- Step
by Step Guide to Build Professional Android Application
- Run
Methods in a Thread
- Interactive
Layout Design
- Creating Action Bar - Menus in Android
- Run
and Test Application in Emulator
- Building
Professional Application "COINS - All Exchange Market Watch"
How to start?
This article is specially written for developers
who are working in C#. Whether they are working in Windows, Web, Silverlight, WPF or
Windows Tablet, it doesn't matter. You need to know C# and be familiar with Visual
Studio.
So here are two things, you need to start
developing Android Apps in C#
If you don't have Visual Studio, you can still use Mono Development Environment that comes with the SDK.
Creating "COINSMobile" Project
As I have already mentioned the article is also for Beginners so I thought I would include a quick guide on how to create Android Project in Visual Studio. You can jump
this step if already have a decent knowledge of using Mono C# for Android.
The download and installation of Mono Android will take a maximum of 25-40 minutes depending
upon your internet speed. Once the SDK is installed, open your Visual Studio.
Click on File>New>Project
You will see a screen below. In the left Pane,
select "Mono for Android". Then select "Android Application". Provide a file name and then click "OK" button.
Here you go and can see the first screen with some generated C# code. As you can see, all the things are quite familiar like namespace, class and also the generated code itself.
The top most part where you see "using namespace" is the default namespace you will be using. If you want to do a special task that is not in the Reference List
you need to use "Add Reference" from the solution explorer to add a component (the same as we would do while coding in C#/VB.NET). Like, if you want to do
an operation with SQLite (light database), you need to add a reference to "Mono.Data.Sqlite
".
One thing I would like to emphasize in the code is the "OnCreate" overridden method
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
StartActivity(typeof(MarketWatchActivity));
}
}
What is OnCreate()?
This
is called when the activity is first created. This is where you should do all
of your normal static set up: creating views, binding data to lists, etc. This
method also provides you with a Bundle containing the activity's previously
frozen state
Oops! Activities, and Views - what are these? I will
explain these a bit later. Let's have a look at the Activity
Life Cycle, which is similar to the ASP.NET Page Life Cycle.
For the time being, think of Activity as a Windows
Form or ASP.NET Page.
If we compare Activity with a web page, Activity
resembles a web page's 'code behind' in ASP.NET. So, just like a web page we
write the code behind in C# and activity is the code behind of Layout.
Layout is an XML called AXML just like in WPF - we
call XAML. If you have prior experience working in WPF or Silverlight, it will be easier to understand AXML Layout.
The application starts with an Activity which will typically has a Layout for displaying the Activity. It clearly defines a Model-View-Controller (MVC) pattern. Layout is the form (or page, or whatever you call
the screen) we see in Android. The language is XML. You need to understand
Layout very thoroughly to build innovative Android applications.
Understanding Files in
Solution Explorer
We will be concentrating on three folders
- Drawable
: containing all graphic contents e.g. images
- Layout
: contains all layouts (screens we see in Android Application);
- Values :
Global variables that we will use throughout the application
Activity1.cs (auto created when you create a project) in the image above is the code
behind of the layout. If you need to create a custom class
you can create a folder under root and place your classes there.
Understanding Layout - UI
Layout is kind of a foreign concept if you are a Windows/Web developer using C#. It will be
easier to understand if you have knowledge in XAML or ASP.NET in MVC.
It doesn't matter if you are new to this Term. I
will try to provide the brief fundamentals of Layouts, but I do prefer to write a
separate article on Layout as this is one of the most important things we need to concentrate on, especially when you are shifting from Windows/Web
development to Mobile.
Let's begin with a scenario where we need to
place a caption in a line, textbox in the second line and a button in the
third line.
It’s simple when you use HTML or aspx to design.
You create three divs, make the div style clear right and place each control
within the div.
Something like in HTML
<div style="clear:right">Code</div>
<div style="clear:right"><Input type="text" id="text1"/></div>
<div style="clear:right"><input type="button" id="button1" value="Ok"/></div>
Here in Android Layout of the above e.g,
You need to play around a bit with the controls
to get a better idea. You can view the controls using View>Toolbox
So in brief, here is the actual definition of
Layout:
Layout is the architecture for the user interface in an
Activity. It defines the layout structure and holds all the elements that
appear to the user.
We can declare layout in two ways:
- Declare
in AXML
- Create controls at runtime
In the "COINS" Application, you will
see - heavy use of creating controls dynamically in Activity Classses.
How many
types of Layout?
- LinearLayout
(Vertical)
- LinearLayout
(Horizontal)
- RelativeLayout
- FrameLayout
- TableLayout
- TableRow
Jump to COINSMobile Application
We have some pretty fair ideas of the Android
Activity Class and Layouts though, I have not gone into details of
Activity.cs. We will gather more in-depth knowledge while showing you step by
step method of creating Professional Android Application.
We will be building primarily five screens:
- Spash Screen
- Market Watch
- Stock Script Detail View
- Add Stock
- Get Stock Quote
- Show Stock Charts
First Step - Connecting to Web Service
We will be using a .NET Web Service to get
real time stock data. Web Services plays an important role to talk to an external
database whatever the database is. If you want to store data into Mobile, you
can use Android's light Database - SQLite. If you would like to talk to
external remote database, you need a Web Service or WCF to talk to the database.
In this example, two Web Methods will be
consumed from COINS Application
- StockExists
- GetStockQuote
- GetStockChart
In the actual code, please replace the URL for web service to point to your localhost service (webservice project uploaded)
The webservice get Market Data Feed from Yahoo API and then uploadthe web service source.
Disclaimer: Although great care has been taken in developing Stock Web Service, it is provided without any guarantee of reliability, accuracy of information, or correctness of operation. I am not responsible for any damage that may occur as a result of using this web service. Use this program entirely at your own risk.
GetStockQuotes()
: Send
multiple stocks in a comma separated string and pass it as a parameter. This
will return the XML of all the Stocks.
StockExist()
: Send
multiple stock scripts in a comma separated string and pass it as a parameter.
This will return the "Exchange" of the Stock. If a stock script is
Invalid, the XML will return "NA" (Not Applicable). Well, we now
have two Web Methods which will be sufficient to create Market Watch for
All Exchange Stock.
Though the web service is an important layer to
fetch the stock details in this Application, the URL provided is used for Proof
of Concept. There is no guarantee that the Web Service used will fetch real
time data.
Well, let's see, how can we consume Web Methods
from Android applications? It is as simple as consuming Web Methods like we all
do in Windows or web applications.
Add a Web Reference to the stock service.
Now we are ready to consume Web Methods. To get the stock data:
using COINSMobile.stockwebservice;
string strXML = string.Empty;
stockwebservice.StockWebservice quoteObject = null;
try
{
quoteObject = new stockwebservice.StockWebservice();
strXML = quoteObject.GetStockQuote(_stocks);
}
finally
{
quoteObject.Dispose();
quoteObject = null;
}
if (strXML.Substring(0, 5) == "error")
{
var t = Toast.MakeText(this, "Error connecting to web service. Please check your Internet connection...", ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
return;
}
if (strXML.ToLower() == "exception")
{
var t = Toast.MakeText(this, "Service not available now. Please try after sometime...", ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
return;
}
XmlDocument doc = new XmlDocument();
doc.LoadXml(strXML);
The important thing to note here is we use the GetStockQuote
method and load the XML string in the XMLDocument
to parse the XML. Thus, you can consume Web Methods to talk to Remote
Database.
This is
not very simple using Android/Java as there are several steps you need to perform in order to consume
.NET Web Service using ksoap.
Before going into further detail, let's see the XML string returned from Wen Method - "GetStockQuote
". Here I will be sending two scripts - "GOOG,MSFT" as an input parameter to the "GetStockQuote()
" web method.
GOOG: Google Inc (Exchange:Nasdaq)
MSFT: Microsoft Corporation (Exchange:Nasdaq)
A Brief Look at SQLite
SQLite is a relational database management
system contained in a small (~350 KiB) C programming library. In contrast to
other database management systems, SQLite is not a separate process that is accessed
from the client application, but is an integral part of it. SQLite is a popular
choice as an embedded database for local/client storage in application software
such as web browsers, Mobile Device, Tablet PC. It is arguably the most widely
deployed database engine, as it is used today by several widespread browsers,
operating systems, and embedded systems.
To start with SQLite, you first need to add a
reference to Mono.Data.Sqlite.dll;
using Mono.Data.Sqlite;
In the "COINS" application, we will be
creating a database and one single Table with subsequent methods to Fetch, Add
and Delete stocks that we will be using Market Watch - Favorite List.
Let's have a look at two classes, one we will be
for creating a Data Repository and the other for all Database Operations.
Stock Class in CRMMobile.Core
Project within the Business Layer.
public class Stock : Java.Lang.Object
{
public long Id { get; set; }
public string StockName { get; set; }
public Stock()
{
Id = -1;
StockName = string.Empty;
}
public Stock(long id, string stockName)
{
Id = id;
StockName = stockName;
}
public override string ToString()
{
return StockName.ToString();
}
}
The class above is simple enough to understand.
Let's have a look in the StockDatabase
class in the DataLayer of the Core Project.
public class StockDatabase
{
private static string db_file = "stockdata.db3";
private string stockName = string.Empty;
private static string sMessage;
public string StockName
{
get { return stockName; }
set { stockName = value; }
}
private static SqliteConnection GetConnection()
{
var dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal)
, db_file);
bool exists = File.Exists(dbPath);
if (!exists)
SqliteConnection.CreateFile(dbPath);
var conn = new SqliteConnection("Data Source=" + dbPath);
if (!exists)
CreateDatabase(conn);
return conn;
}
private static void CreateDatabase(SqliteConnection connection)
{
var sql = "CREATE TABLE STOCKTABLE (Id INTEGER PRIMARY KEY AUTOINCREMENT,
StockName VARCHAR);";
connection.Open();
using (var cmd = connection.CreateCommand())
{
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
connection.Close();
}
public static IEnumerable<Stock> GetStocks()
{
try
{
var sql = "SELECT * FROM STOCKTABLE ORDER BY ID;";
using (var conn = GetConnection())
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
yield return new Stock(reader.GetInt32(0), reader.GetString(1));
}
}
}
}
finally
{
StockManager.Message = sMessage;
}
}
public static bool IsStockExists(string _stockname)
{
bool Ok = false;
var sql = string.Format("SELECT * FROM STOCKTABLE WHERE STOCKNAME='{0}';",
_stockname);
try
{
using (var conn = GetConnection())
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
Ok = true;
}
}
}
}
finally
{
StockManager.Message = sMessage;
}
return Ok;
}
public static bool SaveStock(string _stockname)
{
try
{
bool Ok = IsStockExists(_stockname.Trim().ToUpper());
if (Ok)
{
sMessage = string.Format("Stock Script '{0}' is already added.",
_stockname);
return false;
}
using (var conn = GetConnection())
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
try
{
cmd.CommandText =
"INSERT INTO STOCKTABLE (StockName) VALUES (@StockName);";
cmd.Parameters.AddWithValue("@StockName",
_stockname.ToUpper());
cmd.ExecuteNonQuery();
sMessage = string.Format(
"Stock Script '{0}' is added successfully.", _ stockname.ToUpper());
return true;
}
catch (SqliteException ex)
{
sMessage = ex.Message;
return false;
}
}
}
}
finally
{
StockManager.Message = sMessage;
}
}
public static bool DeleteAllStocks()
{
try
{
using (var conn = GetConnection())
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
try
{
cmd.CommandText = "DELETE FROM STOCKTABLE;";
cmd.ExecuteNonQuery();
sMessage =
"All Stocks are deleted successfully...\nTo view the stocks" +
"in Market Watch, you need to add your custom stock";
return true;
}
catch (SqliteException ex)
{
sMessage = ex.Message;
return false;
}
}
}
}
finally
{
StockManager.Message = sMessage;
}
}
}
I have used SqLite Helper class to do all
database operations. The code is self explained and nothing much within the
code to detail. If you are new to SQLite, just play around with the code to
explore more.
One point to mention: in the application I used
only one Table for adding custom stock script.
CREATE TABLE STOCKTABLE (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
StockName VARCHAR
);
We will be using Web Service and Database quite
frequently in the activity classes.
Now we build the application.
Building Splash Screen (First Tiny Screen with Tiny Code)
Right click solution explorer. Click
Add > New Item > Activity (from Android New Window). Name the file
MainActivity.cs
[Activity(MainLauncher = true, Theme = "@style/Theme.Splash", NoHistory = true)]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
StartActivity(typeof(MarketWatch));
}
}
Let's look at the code
[Activity(MainLauncher = true, Theme = "@style/Theme.Splash", NoHistory = true)]
Main Launcher=True
This means the startup Activity. When the
application loads it launches this activity.
Theme="@style/Theme.Splash"
Did you notice in Solution Explorer there is an
XML file under Resources>Values? This is Strings.xml where we define
global variables that can be accessed by the Application.
To start a theme you need to create a graphics
file and add it in Resources>Drawable. Once you add it, you will see a
reference in "ResourceDesigner.cs" like
public partial class Drawable
{
public const int splash = 2130837511;
.....
}
Here is the XML for splash in strings.xml
<style name="Theme.Splash" parent="android:Theme">
<item name="android:windowBackground">@drawable/splash</item>
<item name="android:windowNoTitle">true</item>
</style>
So, when we write
"@style/Theme.Splash
" - activity points to the splash image. The name
of the image here is splash.png.
StartActivity(typeof(MarketWatch));
- Here MarketWatch
is another Activity class which
is called from splash screen. So, this is the starting point of the
application.
That’s powerful stuff! Isn't it so simple? Just
create a graphics file, add add the file using "Add>Add Existing
Item" from Drawable, add the definition of a graphics file in strings.xml
and write only two lines of code. Fair enough, let's look into the next screen
"Market Watch"
Start with - Market Watch
Here we will be creating a dynamic control
"TableView". The reason for creating a dynamic control is that we are
not sure in design time how many Table Rows we will have. Table Rows will be
created dynamically depending upon the records in the stock table (initially the
stock table is blank, but it will grow subsequently upon adding stock script into
SQLite Database). So, let's have a look at how we create dynamic control in
Android Activity Class.
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="1"
android:id="@+id/deatlWatchLayout" />
</ScrollView>
Note: I have used "ScrollView
" just at
the top of TableLayout. The reason is, if you have stocks that don't fit in
the screen you will see vertical scroll bar, which you can use to
scroll the view.
private void PopulateDataToControls(string _stocks)
{
string strXML = string.Empty;
stockwebservice.StockWebservice quoteObject = null;
try
{
quoteObject = new stockwebservice.StockWebservice();
strXML = quoteObject.GetStockQuote(_stocks);
}
finally
{
quoteObject.Dispose();
quoteObject = null;
}
if (strXML.Substring(0, 5) == "error")
{
var t = Toast.MakeText(this, "Error connecting to web service. Please " +
"check your Internet connection...", ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
return;
}
if (strXML.ToLower() == "exception")
{
var t = Toast.MakeText(this, "Service not available now. " +
"Please try after sometime...", ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
return;
}
XmlDocument doc = new XmlDocument();
doc.LoadXml(strXML);
tablelayout.RemoveAllViews();
tablelayout.RefreshDrawableState();
XmlNodeList xnList = doc.SelectNodes("/stock/symbol");
foreach (XmlNode xn in xnList)
{
if (xn != null)
{
TableRow demoTableRow = new TableRow(this);
TextView tv_l = new TextView(this);
TextView tv_r = new TextView(this);
tv_l.SetPadding(3, 3, 3, 3);
tv_r.SetPadding(3, 3, 3, 3);
tv_r.Gravity = GravityFlags.Right;
tv_l.SetTextSize(Android.Util.ComplexUnitType.Px, 9);
tv_l.Text = xn["code"].InnerText.Trim() +
"-" + xn["exchange"].InnerText.Trim();
tv_r.Text = "(" + xn["change"].InnerText.Trim() +
") " + xn["last"].InnerText.Trim( );
demoTableRow.Clickable = true;
demoTableRow.Click += (sender, e) =>
{
doRowClick(sender);
};
demoTableRow.AddView(tv_l);
demoTableRow.AddView(tv_r);
tablelayout.AddView(demoTableRow);
View vLineRow = new View(this);
vLineRow.SetMinimumHeight(1);
vLineRow.SetBackgroundColor(Color.Rgb(88, 88, 88));
tablelayout.AddView(vLineRow);
}
}
}
The code we will be concentrating on is the
part where we dynamically create TableView controls. TableView is just like a
Table we use in HTML.
TableView contains TableRows. Each Table Row contains TextView
TableRow demoTableRow = new TableRow(this);
TextView tv_l = new TextView(this);
TextView tv_r = new TextView(this);
tv_l.Text = "Column 1 data";
tv_r.Text = "Column 2 data"
demoTableRow.AddView(tv_l);
demoTableRow.AddView(tv_r);
tablelayout.AddView(demoTableRow);
The only tricky part here is to create a Click
Event Handler for the TableView control. Remember, we can have multiple
TableRows in a TableLayout. Each stock script saved in the database will create
one row. So, if we have 20 stock scripts, we will be creating 20 odd Rows by
using a "for loop". To attach an event handler (OnClick) to the TextView,
demoTableRow.Clickable = true;
demoTableRow.Click += (sender, e) =>
{
doRowClick(sender);
}
private void doRowClick(object Sender)
{
TableRow tr = Sender as TableRow;
if (tr != null)
{
TextView v = tr.GetChildAt(0) as TextView;
string _script = v.Text.Trim();
int _index = _script.IndexOf('-');
if (_index >
You have probably noticed I used Intent to pass a value from one
Activity to another.
Intent provides a facility for performing
late runtime binding between the code in different applications. Its most
significant use is in the launching of activities, where it can be thought of
as the glue between activities. It is basically a passive data structure
holding an abstract description of an action to be performed.
So in the StockDetails Activity, we will get the
value that we pass from MarketWatch Activity. In brief, when we click a row, we
pass the sender to the custom method. From sender, we get the TextView Control
and get the "Stock Name". We then pass the value of the Stock Name to
another Activity - "Stock Details Activity" where we will again talk
to Web Service to fetch detailed values of the stock.
Now we have a look into
"MarketWatch.axml" - Layout for Market Watch. The only concerned part in the Layout is to design a TextView at the top left and a refresh the icon
at the top right
Using RelativeLayout
will solve our problem of placing two controls side by side.
<RelativeLayout
android:layout_width="fill_parent"
android:background="@color/medium_gray"
android:layout_height="30dp"
android:paddingTop="3dip">
<TextView
android:id="@+id/symbolcaption"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:textColor="@android:color/white"
android:background="@color/medium_gray"
android:layout_marginLeft="0px"
android:paddingTop="2px" />
<ImageView
android:id="@+id/buttonRefresh"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:lines="1"
android:clickable="true"
android:src="@drawable/refresh_icon" />
<ImageView
android:id="@+id/buttonHome"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="24dip"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:clickable="true"
android:src="@drawable/home_icon" />
</RelativeLayout>
Let's recapitulate, what's this
"@drawable/refresh
" in the ImageView Tag? When you dd a Refresh icon, a
line is generated automatically in Drawable Class in Resource.Designer.cs
public partial class Drawable
{
public const int refresh = 2130837504;
.......
}
So we have seen how
to pass a value from one Activity to another Activity. In the StockDetails Activity,
we will see how we get the value that we pass from the MarketWatch Activity. We also have a fair idea of how to create
Controls dynamically.
Second Step - Build Stock Detail View Using a Press/Touch
In the screen above, you will probably have noticed
that the value we pass from the MarketWatch Activity is to send to Web Service to get
the stock details. Let's look at the code. How will we get the variable and pass it
to Web Service to get the XML?
First, you need to create a New Activity from
"Add > Add Item > Activity". Name the file
"StockDetailsActivity.cs"
_script = Intent.GetStringExtra("Script");
if (_script == string.Empty)
{
var t = Toast.MakeText(this, "Invalid Script...", ToastLength.Long);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
return;
}
ImageView btnRefresh = FindViewById<ImageView>(Resource.Id.buttonRefresh);
btnRefresh.Click += (sender, e) =>
{
if (!IsLoading)
{
ProgressDialog progress = ProgressDialog.Show(this,
"", "Loading Quote...", true);
new Thread(new ThreadStart(() =>
{
this.RunOnUiThread(() =>
{
doLoadDetails();
progress.Dismiss();
});
})).Start();
IsLoading = false;
}
};
ImageView btnHome = FindViewById<ImageView>(Resource.Id.buttonHome);
btnHome.Click += (sender, e) =>
{
StartActivity(typeof(MarketWatchActivity));
return;
};
ProgressDialog progressMain = ProgressDialog.Show(this,
"", "Loading Quote...", true);
new Thread(new ThreadStart(() =>
{
this.RunOnUiThread(() =>
{
doLoadDetails();
progressMain.Dismiss();
});
})).Start();
I hope you have found how we get the value from Indent
.
_script = Intent.GetStringExtra("Script");
if (_script == string.Empty)
{
var t = Toast.MakeText(this, "Invalid Script...", ToastLength.Long);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
return;
}
Intent.GetStringExtra("Script")
will
return the value that we pass from the previous Activity. We will do a simple
check before passing the value to Web Service to pull the real time stock data.
What is
a Toast?
A toast is a view containing a quick little
message for the user. We use "Toast" to display any information or
warning message. The text appears for some time and then vanishes so that users can
have a good idea of what is going on with the application.
Now we will see how we pass the
stock name or script name to a Web Service and pull the XML and show the data in
TableView Controls. This part is very similar to Market Watch.
string strXML = string.Empty;
stockwebservice.StockWebservice quoteObject = null;
try
{
quoteObject = new stockwebservice.StockWebservice();
strXML = quoteObject.GetStockQuote(_script);
}
finally
{
quoteObject.Dispose();
quoteObject = null;
}
if (strXML.Substring(0, 5) == "error")
{
var t = Toast.MakeText(this,
"Error connecting to web service. Please check your Internet connection...", ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
return;
}
if (strXML.ToLower() == "exception")
{
var t = Toast.MakeText(this,
"Service not available now. Please try after sometime...", ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
return;
}
LinearLayout stockdetailsLinearLayout = FindViewById<LinearLayout>(Resource.Id.linearLayoutstockdetails);
symbolCaption = FindViewById<TextView>(Resource.Id.symbolcaption);
symbolCaption.SetTextAppearance(this, Resource.Style.boldText);
priceCaption = FindViewById<TextView>(Resource.Id.pricecaption);
priceCaption.SetTextAppearance(this, Resource.Style.boldText19);
changeCaption = FindViewById<TextView>(Resource.Id.changecaption);
changeCaption.SetTextAppearance(this, Resource.Style.boldText19);
datetimeCaption = FindViewById<TextView>(Resource.Id.datetimecaption);
datetimeCaption.SetTextAppearance(this, Resource.Style.smallText);
XmlDocument doc = new XmlDocument();
doc.LoadXml(strXML);
XmlNodeList xnList = doc.SelectNodes("/stock/symbol");
foreach (XmlNode xn in xnList)
{
data[0, 0] = "Symbol";
data[0, 1] = xn["code"].InnerText.Trim();
data[1, 0] = "Name";
data[1, 1] = xn["company"].InnerText.Trim();
symbolCaption.Text = xn["company"].InnerText.Trim();
datetimeCaption.Text = DateTime.Now.ToString("dd-MM-yyyy hh:mm:ss");
priceCaption.Text = xn["last"].InnerText.Trim() + " " +
xn["currency"].InnerText.Trim();
changeCaption.Text = xn["change"].InnerText.Trim();
View vLinePrice = new View(this);
vLinePrice.SetMinimumHeight(2);
vLinePrice.SetBackgroundColor(Color.Rgb(164, 164, 164));
stockdetailsLinearLayout.AddView(vLinePrice);
data[2, 0] = "Exchange";
data[2, 1] = xn["exchange"].InnerText.Trim();
data[3, 0] = "Open";
data[3, 1] = xn["open"].InnerText.Trim();
data[4, 0] = "Day's High";
data[4, 1] = xn["high"].InnerText.Trim();
data[5, 0] = "Day's Low";
data[5, 1] = xn["low"].InnerText.Trim();
data[6, 0] = "Change";
data[6, 1] = xn["change"].InnerText.Trim();
data[7, 0] = "Change %";
data[7, 1] = xn["changepercent"].InnerText.Trim();
data[8, 0] = "Volume";
data[8, 1] = xn["volume"].InnerText.Trim();
data[9, 0] = "Previous Close";
data[9, 1] = xn["previousclose"].InnerText.Trim();
data[10, 0] = "Trade Time";
data[10, 1] = xn["tradetime"].InnerText.Trim();
data[11, 0] = "Market Capital";
decimal marketcapital = 0;
if (decimal.TryParse(xn["marketcapital"].InnerText.Trim(), out marketcapital))
data[11, 1] = string.Format("{0:#,0}", marketcapital);
else
data[11, 1] = xn["marketcapital"].InnerText.Trim();
TableLayout tableLayout = FindViewById<TableLayout>(Resource.Id.deatlLayout);
tableLayout.RemoveAllViews();
tableLayout.RefreshDrawableState();
for (int i = 2; i < 12; i++)
{
TableRow demoTableRow = new TableRow(this);
TextView tv_l = new TextView(this);
TextView tv_r = new TextView(this);
tv_l.SetPadding(3, 3, 3, 3);
tv_r.SetPadding(3, 3, 3, 3);
tv_r.Gravity = GravityFlags.Right;
tv_l.Text = data[i, 0];
tv_r.Text = data[i, 1];
demoTableRow.AddView(tv_l);
demoTableRow.AddView(tv_r);
if (i == 0)
{
tv_l.SetTextAppearance(this, Resource.Style.boldText);
}
tableLayout.AddView(demoTableRow);
View vLineRow = new View(this);
vLineRow.SetMinimumHeight(1);
vLineRow.SetBackgroundColor(Color.Rgb(88, 88, 88));
tableLayout.AddView(vLineRow);
}
}
The code is self explanatory and there's not much detail to it. I would rather explain the refresh icon functionality, which when clicked or touched, refreshes the view. We will also show a "loading message" the time the application does the background process of calling the web service, pulling the XML data and creating the view with the data. "Refresh" is also done in Market Watch, but I thought it would be nicer to explain that a bit later after understanding - "calling of web service" and "creating controls dynamically".
ImageView btnRefresh = FindViewById<ImageView>(Resource.Id.buttonRefresh);
btnRefresh.Click += (sender, e) =>
{
if (!IsLoading)
{
ProgressDialog progress = ProgressDialog.Show(this, "", "Loading Quote...", true);
new Thread(new ThreadStart(() =>
{
this.RunOnUiThread(() =>
{
doLoadDetails();
progress.Dismiss();
});
})).Start();
IsLoading = false;
}
};
We will be using a new thread to run the method doLoadDetails()
. Before that we will use one extra thing, ProcessDialog
. A progress dialog is part of the Android API and can be used to display either a progress bar or a spinning progress symbol.
So, the basic idea is to show the process dialog first. Then start a new Thread and Run the Method within that thread. Once the method executes, we will Dismiss the process using progress.Dismiss()
.
Third Step - View Stock Chart from Action Bar - Menu
When we are talking about Market Watch, it would not be fair if we don't provide Chart option to user. Users are quite often interested in viewing charts for different Parameters to understand the Stock Movement Trend.
What are the Chart Parameters?
- 1 Day
- 5 Days
- 3 Months
- 6 Months
- 1 Year
- 2 Years
- 5 Years
The Web Method - "GetStockChart" expects two input parameters - Stck Code and Chart Parameters. The method will return the chart image as byte array.
Let's look at the code,
byte[] image_data;
stockwebservice.StockWebservice quoteObject = null;
try
{
quoteObject = new stockwebservice.StockWebservice();
image_data = quoteObject.GetStckChart(_stock, _type);
}
finally
{
quoteObject.Dispose();
quoteObject = null;
}
So, we have now the byte array and how simple it is to convert the byte array to ImageView Control.
Bitmap bitmapChart = BitmapFactory.DecodeByteArray(image_data, 0, image_data.Length);
imgChart.SetImageBitmap(bitmapChart);
imgChart.SetBackgroundResource(Resource.Color.transparent);
Now, we need to place seven buttons and add onClick Handler which when clicked will call the web method - StockGetChart with the different chart parameters.
btn1D.Click += (sender, e) => { doLoadChart(sender, "1D"); };
btn5D.Click += (sender, e) => { doLoadChart(sender, "5D"); };
btn3M.Click += (sender, e) => { doLoadChart(sender, "3M"); };
btn6M.Click += (sender, e) => { doLoadChart(sender, "6M"); };
btn1Y.Click += (sender, e) => { doLoadChart(sender, "1Y"); };
btn2Y.Click += (sender, e) => { doLoadChart(sender, "2Y"); };
btn5Y.Click += (sender, e) => { doLoadChart(sender, "5Y"); };
In the above method "doLoadChart" calls the Web Method - GetStckChart" and return image as byte array. Have a look into the web service code, how image is converted to byte array and send back to client. We will run the method in a Thread to show the Process Dialog till the image is fully loaded in the ImageView Control.
Now before going to the next step, let's have a quick look into the Stock Chart Layout (StckChart.axml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:minWidth="25px"
android:minHeight="25px">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="28dp"
android:id="@+id/linearLayoutstockdetails"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp">
<TextView
android:id="@+id/stockchartcaption"
android:layout_width="206dp"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:textColor="@android:color/white"
android:removed="#000000"
android:layout_marginLeft="0px"
android:textSize="14dp"
android:textStyle="bold"
android:paddingTop="2dp"
android:layout_gravity="center_horizontal" />
<Button
android:text="1D"
android:layout_width="28dp"
android:layout_height="18dp"
android:textSize="12dp"
android:id="@+id/button1D"
android:textColor="#ffffff"
android:padding="3dp"
android:background="#1C1C1C"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginRight="2dp"
android:layout_marginLeft="6dp" />
<Button
android:text="5D"
android:layout_width="28dp"
android:layout_height="18dp"
android:textSize="12dp"
android:textColor="#ffffff"
android:padding="3dp"
android:background="#1C1C1C"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginRight="2dp"
android:id="@+id/button5D" />
<Button
android:text="3M"
android:layout_width="28dp"
android:layout_height="18dp"
android:textSize="12dp"
android:textColor="#ffffff"
android:padding="3dp"
android:background="#1C1C1C"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginRight="2dp"
android:id="@+id/button3M" />
<Button
android:text="6M"
android:layout_width="28dp"
android:layout_height="18dp"
android:textSize="12dp"
android:textColor="#ffffff"
android:padding="3dp"
android:background="#1C1C1C"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginRight="2dp"
android:id="@+id/button6M" />
<Button
android:text="1Y"
android:layout_width="28dp"
android:layout_height="18dp"
android:textSize="12dp"
android:textColor="#ffffff"
android:padding="3dp"
android:background="#1C1C1C"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginRight="2dp"
android:id="@+id/button1Y" />
<Button
android:text="2Y"
android:layout_width="28dp"
android:layout_height="18dp"
android:textSize="12dp"
android:textColor="#ffffff"
android:padding="3dp"
android:background="#1C1C1C"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginRight="2dp"
android:id="@+id/button2Y" />
<Button
android:text="5Y"
android:layout_width="28dp"
android:layout_height="18dp"
android:textSize="12dp"
android:textColor="#ffffff"
android:padding="3dp"
android:background="#1C1C1C"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginRight="2dp"
android:id="@+id/button5Y" />
</LinearLayout>
<ImageView
android:id="@+id/imageChart"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:clickable="true"
android:layout_marginTop="2dp" />
</LinearLayout>
If you see the TextView and the buttons for showing different charts are kept in a LinearLayout with orientation:horizontal. We have already discussed, placing controls in a Linearayout (orientation:horizontal) simply means the control will be placed side by side.
One vital information, I missed in the beginning of this section is we will be forcing the screen to be viewed in Landscape. As the chart will not be best fitted in portrait, we will be forcing the screen to be opened in Landscape.
The simple trick for doing this,
[Activity(Label = "Stock Chart", ScreenOrientation = Android.Content.PM.ScreenOrientation.Landscape)]
Forth Step - Add Custom Stock to SQLite Database
In this Activity we will be adding Stock to SQLite Database. The stocks that are added will be shown in summary in Market Watch and detail in StockDetails Activity.
Let's focus on the screen first.
The Layout for AddStock Activity is pretty simple. Have a look into the AddStock Layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/linearLayout1">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Enter the full Stock Symbol for your exchange. Valid Stock will be added only."
android:layout_marginTop="36px"
android:layout_marginBottom="14px"
android:layout_marginLeft="8px"
android:textSize="13sp"
android:layout_marginRight="8px" />
<EditText
android:layout_width="fill_parent"
android:layout_height="40px"
android:id="@+id/textStock"
android:textColor="@android:color/black"
android:hint="Enter Stock Symbol"
android:layout_marginLeft="8px"
android:layout_marginBottom="12px"
android:layout_marginRight="80px"
android:padding="4px"
android:textColorHint="#323232"
android:textSize="15sp" />
<Button
android:id="@+id/buttonAddStock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Stock..."
android:layout_marginLeft="8px" />
</LinearLayout>
As we are placing the controls one after another, we only need a Linear Layout orientation "Vertical". We have primarily three controls,
You have probably noticed that some padding has been given to get the spacing between each control.
One thing I will stress here is the Hint, i.e. a grayed-out text within the EditText control. This is something like the "placeholder" attribute of a input text in HTML. While designing the screen layout in a mobile device we need to be very sure about the spaces. We don't have too many spaces in a screen to fit all controls. So Hint is often used in Mobile where we get rid of one extra control to let the user identify what the EditText does.
android:hint="Enter Stock Symbol"
android:textColorHint="#323232"
AddStock Activity Class
We will do some basic validation while we click the Add button. Then we will check whether the stock script the user enters is a valid script. If it is not a valid script, we need to display the Message in a Torch. The last important validation is we need to ensure that users don't add duplicate stock. So, we will check whether the Stock Script entered exists in the database.
private bool doCheckStockScript(string _stockscript)
{
string strXML = string.Empty;
stockwebservice.StockWebservice quoteObject = null;
try
{
quoteObject = new stockwebservice.StockWebservice();
strXML = quoteObject.StockExists(_stockscript);
}
finally
{
quoteObject.Dispose();
quoteObject = null;
}
if (strXML.Substring(0, 5) == "error")
{
var t = Toast.MakeText(this,
"Error connecting to web service. Please check your Internet connection...",
ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
return false;
}
if (strXML.ToLower() == "exception")
{
var t = Toast.MakeText(this,
"Service not available now. Please try after sometime...", ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
return false;
}
XmlDocument doc = new XmlDocument();
doc.LoadXml(strXML);
XmlNodeList xnList = doc.SelectNodes("/stock/symbol");
foreach (XmlNode xn in xnList)
{
if (xn["exchange"].InnerText.Trim().ToUpper() == "NA")
return false;
}
return true;
}
private bool AddStockScript(string _stockscript)
{
bool Ok = false;
StockManager.Message = string.Empty;
Ok = StockManager.SaveStock(_stockscript);
if (StockManager.Message != string.Empty)
{
var t = Toast.MakeText(this, StockManager.Message, ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
}
return Ok;
}
Once added successfully we will show a quick message in "Toast" and return back to Market Watch Activity.
Fifth Step - Get Stock Details
Get Stock Details is a customized search of a Stock. The user will enter the script and
press the "Get Stock" button and the stock Quote details are
displayed on the screen. Here is a screen shot of how Get Stock looks after
fetching stock data. In the example I have used script "GOOG" which
is the Stock Script of Google Inc.
Let's look at the GetStockActivity class:
private bool IsLoading = false;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
View titleView = Window.FindViewById(Android.Resource.Id.Title);
if (titleView != null)
{
IViewParent parent = titleView.Parent;
if (parent != null && (parent is View))
{
View parentView = (View)parent;
parentView.SetBackgroundColor(Color.Rgb(28, 28, 28));
parentView.SetMinimumHeight(32);
parentView.SetMinimumHeight(32);
}
}
SetContentView(Resource.Layout.GetStock);
ImageView btnHome = FindViewById<ImageView>(Resource.Id.buttonHomeget);
btnHome.Click += (sender, e) =>
{
StartActivity(typeof(MarketWatchActivity));
return;
};
EditText textStock = FindViewById<EditText>(Resource.Id.textGetStock);
Button btnGetStock = FindViewById<Button>(Resource.Id.buttonGetStock);
btnGetStock.Click += (sender, e) =>
{
if (textStock.Text == string.Empty)
{
var t = Toast.MakeText(this, "Please eneter Stock Script", ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
textStock.Focusable = true;
return;
}
if (doCheckStockScript(textStock.Text.Trim()))
{
ProgressDialog progressMain = ProgressDialog.Show(this, "", "Loading Quote...", tru e);
new Thread(new ThreadStart(() =>
{
this.RunOnUiThread(() =>
{
doLoadDetails(textStock.Text.Trim());
progressMain.Dismiss();
});
})).Start();
}
else
{
var t = Toast.MakeText(this, "You have eneterd invalis Stock Script. Please eneter a valid Stock Script", ToastLength.Short);
t.SetGravity(GravityFlags.Center, 0, 0);
t.Show();
textStock.Focusable = true;
return;
}
};
}
private bool doCheckStockScript(string _stockscript)
{
}
protected void doLoadDetails(string _script)
{
}
Nothing much to explain in details. If you look at the method
"doCheckStockScript()", it checks the validity of the stock
script entered. If the script entered is invalid, a message will pop out
using "Toast".
Wrapping up the Application
So we have almost covered all areas, but there are a few things I didn't discuss earlier in the article, so I will discuss them now. Did you thought while going through the discussion, how we navigate from one page to another (apart from Home icon at the top right)?
There are several ways we follow for the navigation. But depending on the application requirement, I will be using "OptionMenu" to be visible in Action Bar to navigate or jump from one Activity to another.
public override bool OnCreateOptionsMenu(IMenu menu)
{
menu.Add("Get Quote").SetIcon(Resource.Drawable.ic_stock_get); ;
menu.Add("Add Stock").SetIcon(Resource.Drawable.ic_quote_add);
menu.Add("Delete Stocks").SetIcon(Resource.Drawable.ic_stock_delete);
return true;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.TitleFormatted.ToString())
{
case "Delete Stocks":
doDeleteStocks(); break;
case "Add Stock":
doOpenAddStock(); break;
case "Get Quote":
doOpenGetStock(); break;
}
return base.OnOptionsItemSelected(item);
}
protected void doDeleteStocks()
{
StockManager.DeleteAllStocks();
if (!IsLoading)
{
ProgressDialog progress1 = ProgressDialog.Show(this, "", "Deleting Stock...", true);
new Thread(new ThreadStart(() =>
{
this.RunOnUiThread(() =>
{
doLoadMarketWatch();
progress1.Dismiss();
});
})).Start();
IsLoading = false;
};
}
protected void doOpenAddStock()
{
StartActivity(typeof(AddStockActivity));
}
protected void doOpenGetStock()
{
StartActivity(typeof(GetStockActivity));
}
We are overriding the method OnCreateOptionsMenu(IMenu menu). We will use "menu" to create menu items in Action Bar.
IMenu is an interface which exposes three important methods - Add, AddMenu, AddSubMenu. We can further enhance the menu building in an Android application depending upon the requirements.
You are Done - Building Professional Android Application
You have now completed building Professional Android applications. Even though we've only covered a small part of the features and controls I have used in this application, it is within everyone's ability to build upon and enhance their skills to the level of "professional Android developer." This article will give you a fair idea how to create a professional Android application using C# and Visual Studio 2010.
The last step is how you run and test the application. Like all device applications we can run the application in an emulator before installing it into the actual device. In the next section I will describe how to deploy and run the application in the emulator.
Deploy and Run the Application
After you build successfully, go to Debug>Start without Debugging. You can use also Start with Debugging, if you need to debug. Once you click "Start" a window will appear, where you need to select "Start emulator image".
The API Version denotes the Targeted device - supported Android Version I will be selecting API 10, means the Android version supported is 2.3.
Once you select the version and continue, you will find a screen below:
Click "OK" to continue. It will take some time estimated 1 minute to 4 minutes depending upon your Processor speed. So, I would suggest keeping open the emulator while you work with coding. This will save your valuable time. Once the Emulator loads the Activity, it will look like:
Note: Though it is free to run and test the application free in Emulator, you need a License to install the application to the physical device.
Important Notes on Mono Android SDK
The Application is built on Mono Android SDK for Visual Studio 2010.
For Visual Studio 2012, you can download the Mono Android from this link:
Install Mono for Android SDK (Visual Studio 2012).
Please note: If you want to run the application in 2012, you need to convert it to VS 2012 and use the right sdk.
Future Scope
Mobile, iPad, and Tablet PCs are gaining popularity day by day. We are not at all restricted to gaming applications only. We can create powerful business applications for Small/Medium/Enterprise. It is possible to build applications in all the above devices using Mono C#. Apple uses their own O/S iOS. MonoTouch supports C# for writing professional application using Mono C#.
We are already developing Windows Mobile applications in .NET. So, in conclusion, Visual Studio(C#) is the language we can explore to develop cross platform applications in almost all the above devices in today's market.
Comments
Your comments are valuable. I would highly appreciate to have your comments, suggestions and feedbacks. I'll try to be as responsive as I can.