Sunday, October 12, 2008

Android Networking, User Experience and Threads

This tutorial is an android version of good old J2ME tutorial about Networking, User Experience and Threads; thus, I won't make my own J2ME tutorial for that.

Source code can be downloaded below.

For this Simple Http Connection application, it uses the following classes:

Handler
Intent
ProgressBar


Unlike, the hello world tutorial, where only an alert screen shows, this tutorial will have two screens or two activities.
So, I'll get started.

First, create two activity classes for the first screen and the next screen.

SimpleHttpConnection.java is the MAIN activity,
NextScreen.java will display the network response.

Here's what the SimpleHttpConnection (main.xml) looks like.

The white space on top of the buttons is supposed to be the progress bar, for some reason the android eclipse plug-in does not render it properly.

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

private void initComponents() {
connectIntent = new Intent(this, NextScreen.class);
progressBar = (ProgressBar) findViewById(R.id.pbProgressBar);
progressBar.setVisibility(ProgressBar.INVISIBLE);
btnConnect = (Button) findViewById(R.id.btnConnect);
btnConnect.setOnClickListener(this);
btnCancel = (Button) findViewById(R.id.btnCancel);
btnCancel.setOnClickListener(this);
btnCancel.setVisibility(Button.INVISIBLE);
}

Above is the code, for the user interface. Notice that the btnCancel and the progressBar is set to invisible. It will be visible to the user once btnConnect is clicked. Here's what the user-interface will look like.

@Override
public void onClick(View v) {
if (v == btnConnect) {
try {
networkConnection = new NetworkConnection(progressBar,
getString(R.string.simplehttpurl), this,
connectIntent, INTENT_NEXT_SCREEN);
networkConnection.start();
btnConnect.setVisibility(Button.INVISIBLE);
btnCancel.setVisibility(Button.VISIBLE);
} catch (Exception ex) {
Log.e(getClass().getName(), "Error on onClick btnConnect ", ex);
}
} else if(v == btnCancel) {
if(networkConnection != null) {
try {
networkConnection.close();
} catch(Exception ex) {
Log.e(getClass().getName(), "Error on onClick btnCancel ", ex);
}
}
reset();
}
}
When btnConnect is clicked, it initializes the NetworkConnection class and sends the request to the server. The btnConnect button is then set to invisible then the cancel button is set to visible. When the btnCancel is clicked, it will then interrupt the connection, and enables the btnConnect again, disabling the btnCancel and resets the progress bar.

The NetworkConnection class implements Runnable since it will run as another thread.


public class NetworkConnection implements Runnable
Network Connection uses the following parameters:

progressBar - passes the screen's progress bar to be update the progress.
serverUrl - the server's url.
activity - the previous activity, this will be used to call startActivityForResult(intent, intentId);
nextIntent - the next activity to be run after the network connection is done.
intentId - the requestCode used by the Main Activity's onActivityResult method show below.

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.v(getClass().getName(), requestCode + " - " + resultCode);
if (requestCode == INTENT_NEXT_SCREEN) {
reset();
}
}
When the NetworkConnection is started, a thread is created and the HTTP connection starts.

public void start() throws IOException {
runner = new Thread(this);
runner.start();
}

public void run() {
try {

transfer(serverUrl);
} catch (Exception e) {
Log.e(getClass().getName(), "Error on startStreaming ", e);
return;
}
}

public void transfer(String urlStr) throws IOException {
url = new URL(urlStr);
urlConnection = url.openConnection();
inputStream = urlConnection.getInputStream();
if (inputStream == null) {
Log.e(getClass().getName(), "Error on transfer " + url);
}
int contentLength = (int) urlConnection.getContentLength();
if (contentLength == -1)
contentLength = 255;
int ch = 0;
total = contentLength;
Log.v(getClass().getName(), contentLength + "");
StringBuffer buffer = new StringBuffer();
while ((ch = inputStream.read()) != -1) {
buffer.append((char) ch);
fireDataLoadUpdate();
current++;
}
inputStream.close();
response = buffer.toString();
Log.v(getClass().getName(), response);
intent.putExtra(NC_RESPONSE, response);
parent.startActivityForResult(intent, intentId);
}

private void fireDataLoadUpdate() {
Runnable updater = new Runnable() {
public void run() {
double loadProgress = ((double) current / (double) total);
Log.v(getClass().getName(), "loadProgress=" + loadProgress);
progressBar.setProgress((int) (loadProgress * 100));
progressBar.setSecondaryProgress((int) (loadProgress * 100));
Log.v(getClass().getName(), "" + progressBar.getProgress());
}
};
handler.post(updater);
}

The
transfer method, is just a basic HTTP connection, its just opens a connections from the url then opens an input stream. The important part of this method is

intent.putExtra(NC_RESPONSE, response);
parent.startActivityForResult(intent, intentId);

This code, puts the server response in the intent.putExra, then starts the next activity, which was defined on the first screen.

Also, the NetworkConnection has close method, that when the user clicks, it cancels the operation. The method I created is a crude one without any graceful handling. But for this app, it does not need to. If you're app parses data and saves them somewhere, be careful in using this since it will corrupt your data.

public void close() {
if (runner != null) {

if(inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
}
inputStream = null;
}
if(urlConnection != null) {
try {
urlConnection = null;
} catch(Exception ex) {
}
}
try {
runner.interrupt();
runner.join();
runner = null;
} catch (InterruptedException e) {
//Thread.currentThread().interrupt();
}
}
}
NextScreen.java
public class NextScreen extends Activity implements OnClickListener {
private Button btnBack;
private TextView tvMessage;
private String message;

@Override
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.nextscreen);
initComponents();
}

public void initComponents() {
message = getIntent().getStringExtra(NetworkConnection.NC_RESPONSE);
tvMessage = (TextView) findViewById(R.id.tvMessage);
if (message != null) {
tvMessage.setText(message);
} else {
tvMessage.setText("Message is null");
}
btnBack = (Button) findViewById(R.id.btnBack);
btnBack.setOnClickListener(this);
}

@Override
public void onClick(View v) {
if (v == btnBack) {
finish();
}
}
}

On the initComponent method, the Intent Extra is that was set in the NetworkConnection class was retrieved then set on the TextView.

Here's what it looks like.


When the Back button is clicked, the finish() method is called, signifying that the Activity is done. Thus, it goes back to the main screen, and calling the onActivityResult method. For this, application, I just reset the progress bar, and the buttons.

Also, do not forget to add permission, or you might get a Socket Exception.

Modify the Android Manifest and add
uses-permission name="android.permission.INTERNET"

See the AndroidManifest.xml in the source code for more details.

Source Code

5 comments:

Anonymous said...

thanks very much for this great tutorial

Anonymous said...

Brilliant! THANK YOU. I downloaded the project file and it worked the first time - that's SO RARE!! YOU THE MAN - KHARMA++

Mistry Hardik (MSP 2010) said...

Hi, great tutorial...
Just needed a help, i am trying to access MySQL database from a server using the PHP script and parse the json data returned in android but cannot get data.

I am using the snippet from http://www.android10.org/index.php/articlesdatastorage/97-connecting-to-mysql-database

but cannot workout.. can you please help

Thank you for this tutorial and any help you can do.

Regards,
Mistry Hardik
email: mistryhardik.05@gmail.com

Mistry Hardik (MSP 2010) said...

hi,

great work. I just needed a small help, I am trying to access a remote MySQL database using php in my android application,

snippet from:
http://www.android10.org/index.php/articlesdatastorage/97-connecting-to-mysql-database

but the only error returned is nullpointer exception and cannot parse data means data is not returned, but if i access the .php page from webbrowser it works fine..

Thank you in advance.

Regards,
Mistry Hardik

Anonymous said...

Hi,

The source code link is broken, it goes to http://mobileserver.byethost2.com/?p=26

Email

java.padawan@androidph.com