Search This Blog

Monday, November 30, 2009

Yahoo News Search_Part2(FetcherThread)

In the last post we saw how layout file and activity class of our News search application look like; as you might remember our activity class uses a Handler to communicate with the other thread which is responsible to fetch the result of search using Yahoo WebServices. I used Yahoo Java SDK in this case,despite the fact that it has been designed and implemented for Java SE & EE users and you could argue that it's not suitable for Mobile environment which is a totally sensible critisim. however WE ARE JUST ANDROID STUDENTS, i mean we
should take it easy at this stage...
Any way you can see our FetcherThread Class below, when you start a FetcherThread it will wait untill startOperation() method is called, this method has a parameter which repesents search criteria, this method is called by our activity whenever user presses the search button.



package com.news.search;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

import com.yahoo.rest.RestClient;
import com.yahoo.rest.RestException;
import com.yahoo.search.NewsSearchRequest;
import com.yahoo.search.NewsSearchResult;
import com.yahoo.xml.XmlParser;
import com.yahoo.search.xmlparser.XmlParserNewsSearchResults;

public class FetcherThread extends Thread {

public volatile XmlParserNewsSearchResults result = new XmlParserNewsSearchResults();
public volatile boolean lastRequest_finished = true;


private boolean anyRequest = false;
private boolean stopFlag = false;
private NewsSearchRequest request = new NewsSearchRequest();
private Handler callback;



public FetcherThread(Handler handler){
this.callback = handler;
}

public void run(){

request.getParameters().put("appid", "javasdktest");
// request.setResults(15);


do {
while(!this.anyRequest){

try{
synchronized (this) {
this.wait();
}
}catch(InterruptedException exp){
////Just Nothing
}

}

try{

Map results = executeAndParse(request.getRequestUrl(), request.getParameters());
Thread.sleep(4000); ///Just to simulate any possible real world delay
result.parse(results);

}catch(Exception exp){
exp.printStackTrace();

}


Bundle content = new Bundle();
content.putSerializable("result",result.listResults());
Message msg = new Message();
msg.setData(content);
this.callback.sendMessage(msg);

this.anyRequest = false;
this.lastRequest_finished = true;

}while(!this.stopFlag);

}

public synchronized void startOperation(String str){

this.request.setQuery(str);
this.anyRequest = true;
this.lastRequest_finished = false;
this.notifyAll();

}

public void cancelThread(){
this.stopFlag = true;
}


private Map executeAndParse(String serviceUrl, Map parameters) throws Exception {
XmlParser xmlParser = null;

try {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
xmlParser = new XmlParser();

XMLReader reader = parser.getXMLReader();
reader.setContentHandler(xmlParser);
reader.parse(new InputSource(RestClient.call(serviceUrl, parameters)));

}
catch (ParserConfigurationException e) {
throw e;
}
catch (SAXException e) {
throw new SAXException("Error parsing XML response", e);
}
catch (RestException ye) {
throw ye;
}

return xmlParser.getRoot();
}


}



And before i forget it... we know our appilaction will be intracting with internet so we need to announce to the system that we're gonna use network and take the appropriate permission to do that, it's pretty easy, we just gotta add following tag to Android Manifest file :


<uses-permission android:name="android.permission.INTERNET" />


You can see here how this thread is iteracting with our activity using Handler, we send a request to this thread then it starts calling Web Service and extracts information, store them in an array and send the result array back to our activity using Handler.sendMessage() method, the message will be delivered to handleMessage() method when appropriate.
OK...it seems that everything is alright,especially because i tested almost the same code on Desktop Envirnoment and every thing looked pretty good, although i didn't need to do that since Yahoo SDK is tested regularly and there are heaps of people using it out there...
but if you run this application you're gonna be surprised since there will be a wierd bug there, the map that is returned by executeAndParse() method is always gonna be empty....YES...I KNOW.. Why? that one nearly killed me to figure it out and you wouldn't believe this...
let's see how it works...this is the RestClient.Call() method implementation extracted from Yahoo SDK :


public static InputStream call(String serviceUrl, Map parameters) throws IOException, RestException {
StringBuffer urlString = new StringBuffer(serviceUrl);
String query = RestClient.buildQueryString(parameters);

HttpURLConnection conn;
if((urlString.length() + query.length() + 1) > MAX_URI_LENGTH_FOR_GET) {
// Request is too big, do a POST
URL url = new URL(urlString.toString());
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", USER_AGENT_STRING);

conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);
conn.getOutputStream().write(query.getBytes());
}
else {
// Request is small enough it should fit in a GET
if(query.length() > 0) {
urlString.append("?").append(query);
}

URL url = new URL(urlString.toString());
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", USER_AGENT_STRING);

conn.setRequestMethod("GET");
}


int responseCode = conn.getResponseCode();
if (HttpURLConnection.HTTP_OK != responseCode) {
ByteArrayOutputStream errorBuffer = new ByteArrayOutputStream();

int read;
byte[] readBuffer = new byte[ERROR_READ_BUFFER_SIZE];
InputStream errorStream = conn.getErrorStream();
while (-1 != (read = errorStream.read(readBuffer))) {
errorBuffer.write(readBuffer, 0, read);
}

throw new RestException("Request failed, HTTP " + responseCode + ": " + conn.getResponseMessage(), errorBuffer.toByteArray());
}

return conn.getInputStream();
}



an appropriate query is made in this method and Web Service get called, then an InputStream containing XML will be returned, then what we do with that inputStream is to parse the content using a SAX parser...
for SAX Parsing we usually need a Handler, we have used XmlParser class which is the default SAX handler of Yahoo SDK, here is implemtation of three main methods of XmlParser class which extracted from Yahoo SDK :


public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
String parentPath;
if(typeStack.size() < 0) {
parentPath = (String) typeStack.peek();
}
else {
parentPath = "";
}
typeStack.push(parentPath + "/" + qName);

Map newMap = new HashMap();
for (int i = 0; i < attributes.getLength(); i++) {
newMap.put(attributes.getQName(i), attributes.getValue(i));
}

Map top = (Map) stack.peek();
Object obj = top.get(qName);
if (obj == null) {
top.put(qName, newMap);
}
else if (obj instanceof Map) {
List newList = new LinkedList();
newList.add(obj);
newList.add(newMap);
top.put(qName, newList);
}
else if (obj instanceof List) {
((List) obj).add(newMap);
}

stack.push(newMap);

}


public void endElement(String uri, String localName, String qName) throws SAXException {
stack.pop();
typeStack.pop();

}

public void characters(char ch[], int start, int length) throws SAXException {
String current = (String) ((Map) stack.peek()).get("value");
if(current != null) {
current += new String(ch, start, length);
}
else {
current = new String(ch, start, length);
}

((Map) stack.peek()).put("value", current);
}



(You might be thinking that those methods have been implemented a bit weird, yes
..because they have been implemented to handle all Yahoo Web Services and not
only News Web Service).
I bet you will be surprised if i tell you that problem is here. actually i had assumed if we have got Java SE APIs in android it would mean that you can expect them to work exactly like it does in Java SE, but apparently it is not correct, take a second look at startElement() method , we normally use qName to get tag names just like it's being used here, but the value of qName is an empty String in android environment!! all we need to do is to replace all accurances of qName with localName and it will be fine then, so our new implementation will be
like this:


public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
String parentPath;
if(typeStack.size() < 0) {
parentPath = (String) typeStack.peek();
}
else {
parentPath = "";
}
typeStack.push(parentPath + "/" + localName);

Map newMap = new HashMap();
for (int i = 0; i < attributes.getLength(); i++) {
newMap.put(attributes.getLocalName(i), attributes.getValue(i));
}

Map top = (Map) stack.peek();
Object obj = top.get(localName);
if (obj == null) {
top.put(localName, newMap);
}
else if (obj instanceof Map) {
List newList = new LinkedList();
newList.add(obj);
newList.add(newMap);
top.put(localName, newList);
}
else if (obj instanceof List) {
((List) obj).add(newMap);
}

stack.push(newMap);

}



although it took a long time to me to find out what was wrong, it was worthwhile since i will be more carefull about how i think some java APIs should work from my exprience and how they actually work within android environment.
i mean it would not hurt to have a second look at API documentation before using them, even though you have had some exprience working with them before.
XML Parsing was not addressed in details in our application, obviously because i used Yahoo SDK to make things easier, but if you got interested in XML Parsing you can find a beautiful document here, besides i'm probabaly gonna change this application to see how we can use Pull Parser instead of SAX Parser.










Yahoo News Search_Part1(Activity & GUI)

I had been thinking to find an idea for my next application, something simple but kinda real and usefull at the same time.
and finally came up with this...News Search Application, it's simple...you enter what you are interested to find out more about and then the application will use Yahoo News Search WebService to see what can be found for you. how does it sound? sounds like fun to me....
so let's get down to it...
Our GUI will be so simple, a text box, a search button and a box to show the result of search , Like this :





for having that our layout file should be something like this :

<?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"
>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="10dip"
android:gravity="center">


<EditText android:text=" "
android:id="@+id/EditText01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="400dip"
android:gravity="top"
android:minWidth="230dip"></EditText>

<Button android:text="@string/searchBtn"
android:id="@+id/Button01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"></Button>

</LinearLayout>



<TextView android:text=" " android:id="@+id/TextView01"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:layout_width="fill_parent"
android:layout_margin="10dip"
android:background="@color/white"
android:textColor="@color/black"
android:padding="5dip"
android:fadeScrollbars="false"
android:scrollbars="vertical" ></TextView>



</LinearLayout>



it's pretty simple and only thing that might seem a bit confusing are those gravity attributes of textbox and button, I know...
actually I had some struggles to align the textbox and button and finally that's what i came up with...I've always had some problems with designing GUIs though ;).


The next thing we would probably need to be worried about is how to handle network communication, i mean it's not a good idea to handle network communication inside our main activity since it will block our main thread, therefore we gotta have another thread
dealing with network and fetch the result of search using Yahoo WebService, but how should our main thread interact with this new thread... i reckon the best way would be like having the second thread running , then when user presses the search button we will send
the search criteria for that thread, it will then call the WebService and send the result back to our main activity where we can show that in the viewbox with appropriate format , but the whole interaction between our activity and the other thread gotta be Asynchronous, otherwise there is no point using separate thread. Thankfully Android has provided something very handy for Asynchronous thread interactions which is called Handler, the concept is pretty simple : a thread can define a Handler to recieve Asynchronous messages from other threads then each thread that want to send messages can get that Handler and send messages whenever appropriate without the first thread having to wait for the second one...
was it simple? ;) i did my best to put it in a simple way,although sometimes we need something more than words to define simplicity....
here is what our Activity class will look like...



package com.news.search;

import com.yahoo.search.NewsSearchResult;
import com.yahoo.search.xmlparser.XmlParserNewsSearchResults;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener{

private MyHandler handler = new MyHandler();
private FetcherThread thread;
private TextView view;
EditText field;


/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

thread = new FetcherThread(this.handler);
thread.start();

this.view = (TextView)findViewById(R.id.TextView01);
this.view.setFocusable(true); //Remember to mention this
this.view.setFocusableInTouchMode(true);
this.view.setMovementMethod(ScrollingMovementMethod.getInstance());
this.field = (EditText)findViewById(R.id.EditText01);
findViewById(R.id.Button01).setOnClickListener(this);

}

@Override
public void onSaveInstanceState(Bundle instanceState){

}

@Override
public void onRestart(){
super.onRestart();
}

@Override
public void onStart(){
super.onStart();
}

@Override
public void onResume(){
super.onResume();

}

@Override
public void onPause(){
super.onPause();

}

@Override
public void onStop(){
super.onStop();

}

@Override
public void onDestroy(){
this.thread.cancelThread();
super.onDestroy();

}

@Override
public void onClick(View v) {
if(this.thread.lastRequest_finished){
this.thread.startOperation(this.field.getText().toString());
this.view.setText(" \n\nPLEASE WAIT...");
}
}


class MyHandler extends Handler {

public void handleMessage(Message msg){

Bundle bundle = msg.getData();
NewsSearchResult myResult[] = (NewsSearchResult[])bundle.getSerializable("result");

if(myResult == null || myResult.length == 0){
view.setText("\n *NO MATCH COULD BE FOUND* ");
return;
}

StringBuffer buffer = new StringBuffer();
for(NewsSearchResult temp : myResult){
buffer.append("TITLE :"+temp.getTitle()+"\n");
buffer.append("SUMMARY : "+temp.getSummary()+"\n\t----------\n");
}


view.setText(buffer.toString());
view.requestFocus();

}

}

}





although we dont need activity life cycle methods implemented for this application, i put them there purposely just to remind us the lifecycle of activities
sorry if it sounds a bit ridiculous , maybe i'm just a little bit paranoid about the activity lifecycle but i feel like we should not forget it...
any way...that's all we needed in our Activity class....it seemed to be more complicated at first, didn't it? ;)

you might be thinking about why i used setFocusable() and setMovementMethod(), well initially i did not , but i realized that i couldn't scroll the viewbox content
using arrow keys(at least using emulator, i'm not sure about real android devices though) so i added those three line of codes and got the problem solved.

i'm gonna talk about FetcherThread in my next post and you will see how smoothly these two threads are working together without any blocking problem.

Saturday, November 28, 2009

First Application_Part3 (Activities)

We have got our View and Model so far, it means that we've got one more thing left to do, our controller which is called Activity in Android world. we will need 3 activities since we have 3 different functionality in our application, our calculator , Save result page and View Page. you might say , well it's actually one main application with three functions and that's what i though initially, but apparently we need a different activity for each of them , i'm not sure though... but i kinda like this idea...real modularity.
Any way you can see our main activity below, it's a bit messy but what i was gonna do was to find out how many ways there are for handling events...you can see that i have used 3 different ways to handling onClick event for our calculator's buttons. if you cannot figure out what oneClicked , twoClicked and... are. you would be better to take a look at our calculator layout file which you can find at First Application_Part1 .



package com.amir.calculator;

import com.amir.calculator.Calculator.Operation;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener{
/** Called when the activity is first created. */

private TextView textView;
private double currentValue = 0;
private Button addBtn;
private Button subBtn;
private Button divBtn;
private Button mulBtn;
private Button SRBtn;
private Button clearBtn;
private Button showBtn;
private Button equal;
private Calculator calc = new Calculator();

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

this.textView = (TextView)findViewById(R.id.note);
this.textView.setText(Double.toString(this.currentValue));
this.addBtn = (Button)findViewById(R.id.addition);
this.subBtn = (Button)findViewById(R.id.subtract);
this.divBtn = (Button)findViewById(R.id.devide);
this.mulBtn = (Button)findViewById(R.id.multiply);
this.equal = (Button)findViewById(R.id.equalSign);
this.SRBtn = (Button)findViewById(R.id.save);
this.clearBtn = (Button)findViewById(R.id.clear);
this.showBtn = (Button)findViewById(R.id.show);

this.addBtn.setOnClickListener(this);
this.subBtn.setOnClickListener(this);
this.divBtn.setOnClickListener(this);
this.mulBtn.setOnClickListener(this);
this.equal.setOnClickListener(this);

this.SRBtn.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {

Intent intent = new Intent();
intent.setAction("com.amir.calculator.SAVE");
intent.putExtra("com.amir.calculator.result", MainActivity.this.currentValue);
startActivity(intent);

}
});

this.clearBtn.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {

MainActivity.this.textView.setText("");
calc.resetCalculator();
MainActivity.this.currentValue = 0;

}
});


this.showBtn.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {

Intent intent = new Intent();
intent.setAction("com.amir.calculator.SHOW");
startActivity(intent);

}
});

}

@Override
public void onClick(View source) {

if(this.currentValue == 0)
return;

if(!this.calc.isStarted()){
this.calc.initiateProcess(this.currentValue);
}
else if (source.getId() != R.id.equalSign){ //to support cascading operations...
this.currentValue = doCalculation();
this.calc.initiateProcess(this.currentValue);
}

if(source.getId() == R.id.addition){
this.textView.setText("+");
this.calc.nextOperation(Calculator.Operation.ADD);
this.currentValue = 0;
}
else if(source.getId() == R.id.subtract){
this.textView.setText("-");
this.calc.nextOperation(Calculator.Operation.SUBTRACT);
this.currentValue = 0;
}
else if(source.getId() == R.id.devide){
this.textView.setText("/");
this.calc.nextOperation(Calculator.Operation.DIVIDE);
this.currentValue = 0;
}
else if(source.getId() == R.id.multiply){
this.textView.setText("*");
this.calc.nextOperation(Calculator.Operation.MULTIPLY);
this.currentValue = 0;
}
else if(source.getId() == R.id.equalSign){
this.textView.setText(Double.toString(doCalculation()));
this.calc.resetCalculator();
}
else
this.textView.setText("ERROR!!!");

}

private double doCalculation(){
double result = this.calc.doOperation(this.currentValue);
this.currentValue = result;

return result;
}

public void oneClicked(View btn){
this.recalculateValue(1);
}

public void twoClicked(View btn){
this.recalculateValue(2);
}

public void threeClicked(View btn){
this.recalculateValue(3);
}

public void fourClicked(View btn){
this.recalculateValue(4);
}

public void fiveClicked(View btn){
this.recalculateValue(5);
}

public void sixClicked(View btn){
this.recalculateValue(6);
}

public void sevenClicked(View btn){
this.recalculateValue(7);
}

public void eightClicked(View btn){
this.recalculateValue(8);
}

public void nineClicked(View btn){
this.recalculateValue(9);
}

public void zeroClicked(View btn){
this.recalculateValue(0);
}

private void recalculateValue(int value){
this.currentValue = (this.currentValue*10)+value;
this.textView.setText(Double.toString(this.currentValue));

}

}




it might not be the best algorithm for implementing a calculator , but i'm not worried about that...at least at this stage ;)
you can also see how we interact with other activities in android, when user presses SR button we need to run other activity to Save the result so what we need to do is to send a message for that activity with all required information and ask it to do what it's supposed to do... startActivity() function is responsible to deliver our messages to other activities and Intent class represents our messages(receiver address and required information).

and here is our Save Result Activity :



package com.amir.calculator;

import java.util.Calendar;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class SaveActivity extends Activity implements OnClickListener{

private double value;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.save);

this.value = getIntent().getDoubleExtra("com.amir.calculator.result", 0);
TextView view = (TextView)findViewById(R.id.ResultBox);
view.setText(Double.toString(this.value));

Button btn = (Button)findViewById(R.id.SaveBtn);
btn.setOnClickListener(this);

}

@Override
public void onClick(View v) {

ContentValues values = new ContentValues();
EditText text = (EditText)findViewById(R.id.content);
String content = text.getText().toString();

if( content == null || content.length() == 0){
text.setText("DefaultName - "+Calendar.getInstance().getTime().toString());
return;
}

values.put(CalcContent.Content.NOTE, content);
values.put(CalcContent.Content.VALUE, this.value);
getContentResolver().insert(CalcContent.Content.CONTENT_URI, values);

finish();

}


}


the most interesting thing here would probably be how we saved our data, we have already implemented our ContentProvider and also defined it in our Android Manifest file, so all we need to do is to use getContentResolver() function, it will then go and start searching Android manifest file to find a ContentProvider which is able to handle requested data type , in this case
CalcContent Data type.

Our last activity is 'Show Saved Results' :




package com.amir.calculator;

import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class ShowActivity extends Activity {


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.show);
TextView view = (TextView)findViewById(R.id.viewBox);


Cursor cursor = getContentResolver().query(CalcContent.Content.CONTENT_URI, null, null, null, null);
int number = 0;

if(cursor.moveToFirst()){
int noteIndex = cursor.getColumnIndex(CalcContent.Content.NOTE);
int valueIndex = cursor.getColumnIndex(CalcContent.Content.VALUE);

String str = " ";
do{
str += cursor.getString(noteIndex)+" -> "+cursor.getDouble(valueIndex)+"\n";
}while(cursor.moveToNext());

view.setText(str);
}
else{
view.setText("Data Base is empty mate!!");
}


}



}



There are heaps of thing to learn about Activities and their life cycle which I'm gonna get into them soon in my next application...but before that i will be going over Activity class Documentation again and again ;)

The last thing that we'll need to complete our application is Android Manifest file :


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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.amir.calculator">

<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="false" />


<application android:icon="@drawable/icon" android:label="@string/app_name">



<provider android:name="CalcProvider"
android:authorities="com.amir.calculator.CalcContent"
/>


<activity android:name="MainActivity"
android:label="@string/app_name"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name="SaveActivity"
android:label="@string/save"
android:theme="@android:style/Theme.Dialog">
<intent-filter>
<action android:name="com.amir.calculator.SAVE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>


<activity android:name="ShowActivity"
android:label="@string/show"
android:theme="@android:style/Theme.Dialog">
<intent-filter>
<action android:name="com.amir.calculator.SHOW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>


</application>
<uses-sdk android:minSdkVersion="5" />

</manifest>

Friday, November 27, 2009

First Application_Part 2 (Content Provider)

We will need to store and retrieve data in our application and what Android can do in this area is really impressive, at least for me ;). we can use both file or database to store and manipulate information... android has provided a mechanism called 'ContentProvider', it's actually kind of Facade which helps to keep the interface of data communication structure independent from whatever is happening behind the scene , whether you are using database or file or if you are using caching or data validation...the interface will be always the same....

I'm gonna use database facilities of Android for this application, speaking of database...the first question that might pop into your mind would be 'what about a mapping tool?' especially if your are an enterprise system developer i bet you cannot even imagine working with database without Hibernate or at least IBatis... but we should remember that we are dealing with mobile phones and not those incredibly powerful servers which are used in enterprise systems... i mean even the ability to work with database in these tiny devices really amazes me specially when you find out there is actually some kind of simple mapping structure to standardize and simplify working with database in android...

OK..let's see what i am talking about. we want to store the result of calculation and a title which user assigns to that using our 'Save Result Dialog'. So what will be storing in database are the result, title and some general information like , say , creation date and modification date.
We will need a class like this :



package com.amir.calculator;

import android.net.Uri;
import android.provider.BaseColumns;

public class CalcContent {

public static final String AUTHORITY = "com.amir.calculator.CalcContent";


private CalcContent() {}


public static final class Content implements BaseColumns {

private Content() {}

public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/notes");


public static final String CONTENT_ITEM_TYPE = "vnd.amir.item/vnd.calc.content";


public static final String DEFAULT_SORT_ORDER = "modified DESC";


public static final String NOTE = "note";

public static final String VALUE = "value";

public static final String CREATED_DATE = "created";

public static final String MODIFIED_DATE = "modified";
}


}



CONTENT_URI represents the Identity of our Data Type and will be used
wherever we need to work with this type of data as we will see soon.

Next step would be Content Provider :


package com.amir.calculator;


import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.util.Log;

public class CalcProvider extends ContentProvider {


private final static String DB_NAME = "Calc.db";
private final static String TABLE_NAME = "CalcNotes";
private DatabaseHelper helper;


private static class DatabaseHelper extends SQLiteOpenHelper {

DatabaseHelper(Context context) {
super(context, DB_NAME , null,1);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
+ CalcContent.Content._ID + " INTEGER PRIMARY KEY,"
+ CalcContent.Content.NOTE + " TEXT,"
+ CalcContent.Content.VALUE + " NUMBER,"
+ CalcContent.Content.CREATED_DATE + " INTEGER,"
+ CalcContent.Content.MODIFIED_DATE + " INTEGER"
+ ");");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w("MyProvider", "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS notes");
onCreate(db);
}
}


@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
return 0;
}

@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}

@Override
public Uri insert(Uri uri, ContentValues initialValues) {

ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}

Long now = Long.valueOf(System.currentTimeMillis());

// Make sure that the fields are all set
if (values.containsKey(CalcContent.Content.CREATED_DATE) == false) {
values.put(CalcContent.Content.CREATED_DATE, now);
}

if (values.containsKey(CalcContent.Content.MODIFIED_DATE) == false) {
values.put(CalcContent.Content.MODIFIED_DATE, now);
}

if (values.containsKey(CalcContent.Content.NOTE) == false) {
values.put(CalcContent.Content.NOTE, "");
}

if(values.containsKey(CalcContent.Content.VALUE) == false){
values.put(CalcContent.Content.VALUE,0.0);
}

SQLiteDatabase db = helper.getWritableDatabase();
long rowId = db.insert(TABLE_NAME, CalcContent.Content.NOTE, values);
if (rowId > 0) {
Uri noteUri = ContentUris.withAppendedId(CalcContent.Content.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}

throw new SQLException("Failed to insert row into " + uri);

}

@Override
public boolean onCreate() {
this.helper = new DatabaseHelper(getContext());
return true;
}

@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {

SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(TABLE_NAME);
SQLiteDatabase db = helper.getReadableDatabase();
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);

// Tell the cursor what uri to watch, so it knows when its source data changes
c.setNotificationUri(getContext().getContentResolver(), uri);

return c;

}

@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}

}



From now on any application which want to work with 'CalcContent' Data Type can simply use this Content Provider.the last thing we need to do is to define this Content Provider in Android Manifest file so that activities can use it. it would be pretty easy :


<provider android:name="CalcProvider"
android:authorities="com.amir.calculator.CalcContent"
/>

You can have most of your questions about 'ContentProvider' answered here. although i believe as long as you are not writing some real code you don't really know what's your question ;)

First Application_Part1 (GUI)

I though It'd be good to start developing a couple of simple application to see how this whole thing works...so First application will be a simple calculator and it's gonna look like this :




A Simple Calculator which will also be able to Save the results of calculations(SR Button) and users will be able to view what have been stored(Show Saved Data Button).
Android has provided a XML_Based Mechanism for designing GUI, you define your XML file and then bind it to an activity, that activity will interpret XML files into android components.
in this case our XML will be like this :

<?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">


<TextView android:id="@+id/note"
android:maxLines="1"
android:minLines="1"
android:layout_marginTop="2dip"
android:layout_width="wrap_content"
android:ems="25"
android:layout_height="wrap_content"
android:autoText="true"
android:capitalize="sentences"
android:singleLine="true"
android:cursorVisible="true"
android:layout_gravity="center_vertical|center_horizontal|center"
android:layout_margin="10px"
android:padding="5px"
android:background="@color/white"
android:textColor="@color/black"/>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal|center">


<Button android:id="@+id/one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/number_One"
android:layout_gravity="center_horizontal"
android:onClick="oneClicked"
android:minWidth="70px"/>


<Button android:id="@+id/two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/number_Two"
android:layout_gravity="center_horizontal"
android:onClick="twoClicked"
android:minWidth="70px"/>

<Button android:id="@+id/three"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/number_Three"
android:layout_gravity="center_horizontal"
android:onClick="threeClicked"
android:minWidth="70px"/>


<Button android:id="@+id/devide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/devide_sign"
android:layout_gravity="center_horizontal"
android:minWidth="70px"/>


</LinearLayout>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal|center">


<Button android:id="@+id/four"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/number_Four"
android:layout_gravity="center_horizontal"
android:onClick="fourClicked"
android:minWidth="70px"/>


<Button android:id="@+id/five"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/number_Five"
android:layout_gravity="center_horizontal"
android:onClick="fiveClicked"
android:minWidth="70px"/>

<Button android:id="@+id/six"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/number_Six"
android:layout_gravity="center_horizontal"
android:onClick="sixClicked"
android:minWidth="70px"/>


<Button android:id="@+id/multiply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/multiply_sign"
android:layout_gravity="center_horizontal"
android:minWidth="70px"/>


</LinearLayout>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal|center">


<Button android:id="@+id/seven"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/number_Seven"
android:layout_gravity="center_horizontal"
android:onClick="sevenClicked"
android:minWidth="70px"/>


<Button android:id="@+id/eight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/number_Eight"
android:layout_gravity="center_horizontal"
android:onClick="eightClicked"
android:minWidth="70px"/>

<Button android:id="@+id/nine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/number_Nine"
android:layout_gravity="center_horizontal"
android:onClick="nineClicked"
android:minWidth="70px"/>


<Button android:id="@+id/subtract"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/subtract_sign"
android:layout_gravity="center_horizontal"
android:minWidth="70px"/>

</LinearLayout>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal|center">


<Button android:id="@+id/zero"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/number_Zero"
android:layout_gravity="center_horizontal"
android:onClick="zeroClicked"
android:minWidth="70px"/>


<Button android:id="@+id/save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save_btn"
android:layout_gravity="center_horizontal"
android:minWidth="70px"/>


<Button android:id="@+id/equalSign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/equal_sign"
android:layout_gravity="center_horizontal"
android:minWidth="70px"/>


<Button android:id="@+id/addition"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/addition_sign"
android:layout_gravity="center_horizontal"
android:minWidth="70px"/>


</LinearLayout>

<Button android:id="@+id/clear"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/clear"
android:layout_gravity="center_horizontal"
android:minWidth="70px"
android:layout_marginLeft="18px"
android:layout_marginRight="18px" />

<Button android:id="@+id/show"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/show"
android:layout_gravity="center_horizontal"
android:minWidth="70px"
android:layout_marginLeft="18px"
android:layout_marginRight="18px" />


</LinearLayout>


When user presses SR button a dialog should get opened asking for a title with which the result of calculation will be saved. This dialog will look like this :




We would need an Layout file like this for what we see above:

<?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="300px">



<TextView android:text="@string/empty" android:id="@+id/ResultBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="top"
android:layout_margin="15px"
android:padding="5px"/>



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="15px">

<TextView android:text="@string/Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="top"/>


<EditText android:id="@+id/content"
android:maxLines="5"
android:minLines="5"
android:minWidth="200px"
android:layout_width="fill_parent"
android:ems="30"
android:layout_height="wrap_content"
android:autoText="true"
android:cursorVisible="true"/>


</LinearLayout>

<Button android:id="@+id/SaveBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/saveBtn"
android:layout_gravity="center_horizontal"
android:minWidth="120px"/>

</LinearLayout>


When user presses 'Show Saved Data' we will need a simple dialog box just to show all values that have been saved, therefore something like this would be good enough :


<?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="300px">



<TextView android:id="@+id/viewBox"
android:maxLines="15"
android:minLines="5"
android:layout_marginTop="2dip"
android:layout_width="wrap_content"
android:ems="25"
android:layout_height="wrap_content"
android:autoText="true"
android:capitalize="sentences"
android:scrollHorizontally="true"/>




</LinearLayout>


Remember that all values with '@String' prefix need to be defined in a resource file named string.xml which apparently must be placed in 'values' directory of your project (layouts files must go under 'layout' directory as well).