Monday, November 24, 2008

Camera Capture

Source code can be downloaded below.

Before creating the Image Capture application, the SDCard must be configured first in order to be able to save in the Picture Gallery of the emulator.

Creating the SDCARD

1. See the emulator -help-sdcard to see where the default sdcard.img is located. Create if necessary.



2. Type mksdcard in the command line to know the required switches in the command-line.


3. Make SD Card.


4. For my SD Card, I just went to the default folder on step#1 and created the default SD-Card.
-cd to the default sd directory
-mksdcard 16M sdcard.img

*Note: the minumum SD required by the emulator is 8Megabytes, I just used 16M to make sure. Also, the higher the size of the SD, the longer it takes to create. So, when you create a 4GB SD, the command line might seem unresponsive.


I've been trying to figure out how to capture images in android and storing it in the picture gallery for 2 weeks now.

I just discovered, that there is nothing wrong with the code, but there's something wrong with the emulator.

For one thing, I keep on getting this image, even when using the built in Camera Application on the main menu. But, I think it is normal. Is it?


The problem I am having is that when I capture an image in the application, the File Explorer (sdcard/dcim/Camera) does save the image file. But when I check the gallery, it does not seem to exist. I then try to push an image to the emulator's file system (sdcard/dcim/Camera), but still the Picture Gallery does not reflect. Below, is a screenshot of what (sdcard/dcim/Camera) contains and what shows in the emulator's picture gallery.



When I restarted the emulator, the Picture Gallery reflected the content's of the file system. Is this a bug or did I just missed a step?


Anyways, here's the code I compiled from various sources using Google Android SDK 1R1.

http://www.anddev.org/viewtopic.php?p=704#704
http://www.anddev.org/viewtopic.php?p=645#645

I also used another site but I forgot the link. So if you see your code here, just drop me a message and I'll link you up as reference. I think the file I downloaded was CameraTestApi.zip but was based on an older SDK. I apologize for the author of the code that I based my revised Image Capture application.


Here the code for the Image Capture Application.

1. Create two classes.
ImageCapture.java


public class ImageCapture extends Activity implements SurfaceHolder.Callback
{
private Camera camera;
private boolean isPreviewRunning = false;
private SimpleDateFormat timeStampFormat = new SimpleDateFormat("yyyyMMddHHmmssSS");

private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;
private Uri target = Media.EXTERNAL_CONTENT_URI;

public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
Log.e(getClass().getSimpleName(), "onCreate");
getWindow().setFormat(PixelFormat.TRANSLUCENT);
setContentView(R.layout.main);
surfaceView = (SurfaceView)findViewById(R.id.surface);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

public boolean onCreateOptionsMenu(android.view.Menu menu) {
MenuItem item = menu.add(0, 0, 0, "goto gallery");
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
Intent intent = new Intent(Intent.ACTION_VIEW, target);
startActivity(intent);
return true;
}
});
return true;
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
}

Camera.PictureCallback mPictureCallbackRaw = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera c) {
Log.e(getClass().getSimpleName(), "PICTURE CALLBACK RAW: " + data);
camera.startPreview();
}
};

Camera.PictureCallback mPictureCallbackJpeg= new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera c) {
Log.e(getClass().getSimpleName(), "PICTURE CALLBACK JPEG: data.length = " + data);
}
};

Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
public void onShutter() {
Log.e(getClass().getSimpleName(), "SHUTTER CALLBACK");
}
};


public boolean onKeyDown(int keyCode, KeyEvent event)
{
ImageCaptureCallback iccb = null;
if(keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
try {
String filename = timeStampFormat.format(new Date());
ContentValues values = new ContentValues();
values.put(Media.TITLE, filename);
values.put(Media.DESCRIPTION, "Image capture by camera");
Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
//String filename = timeStampFormat.format(new Date());
iccb = new ImageCaptureCallback( getContentResolver().openOutputStream(uri));
} catch(Exception ex ){
ex.printStackTrace();
Log.e(getClass().getSimpleName(), ex.getMessage(), ex);
}
}
if (keyCode == KeyEvent.KEYCODE_BACK) {
return super.onKeyDown(keyCode, event);
}

if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
camera.takePicture(mShutterCallback, mPictureCallbackRaw, iccb);
return true;
}

return false;
}

protected void onResume()
{
Log.e(getClass().getSimpleName(), "onResume");
super.onResume();
}

protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
}

protected void onStop()
{
Log.e(getClass().getSimpleName(), "onStop");
super.onStop();
}

public void surfaceCreated(SurfaceHolder holder)
{
Log.e(getClass().getSimpleName(), "surfaceCreated");
camera = Camera.open();
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
{
Log.e(getClass().getSimpleName(), "surfaceChanged");
if (isPreviewRunning) {
camera.stopPreview();
}
Camera.Parameters p = camera.getParameters();
p.setPreviewSize(w, h);
camera.setParameters(p);
camera.setPreviewDisplay(holder);
camera.startPreview();
isPreviewRunning = true;
}

public void surfaceDestroyed(SurfaceHolder holder)
{
Log.e(getClass().getSimpleName(), "surfaceDestroyed");
camera.stopPreview();
isPreviewRunning = false;
camera.release();
}
}


ImageCaptureCallback.java


public class ImageCaptureCallback implements PictureCallback {

private OutputStream filoutputStream;
public ImageCaptureCallback(OutputStream filoutputStream) {
this.filoutputStream = filoutputStream;
}
@Override
public void onPictureTaken(byte[] data, Camera camera) {
try {
Log.v(getClass().getSimpleName(), "onPictureTaken=" + data + " length = " + data.length);
filoutputStream.write(data);
filoutputStream.flush();
filoutputStream.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}


2. Create the main.xml as shown below









3. Add a camera permission to the ApplicationManifest




Source Code

Monday, October 27, 2008

Android Map Viewer

1. Create MapViewer Project
- Create MapViewer class that extends MapActivity


package javapadawan.android;

import android.os.Bundle;
import android.view.KeyEvent;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;

public class MapViewer extends MapActivity {
private MapView map;
private MapController mc;
private static int zoomValue = 1;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
map = (MapView) findViewById(R.id.map);
mc = map.getController();
mc.setZoom(zoomValue);
}

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
zoomValue = zoomValue + 1;
mc.setZoom(zoomValue);
} else if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
zoomValue = zoomValue - 1;
mc.setZoom(zoomValue);
} else if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
map.setSatellite(false);
} else if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
map.setSatellite(true);
}
return super.dispatchKeyEvent(event);
}

@Override
protected boolean isRouteDisplayed() {
return false;
}
}


2. Modify main.xml







3. Set the required permissions in the AndroidManifest.xml


















4. Clean, Build and Run the Project. You will see an Empty Map Grid instead of a google map.



4. The reason why the code does not work is that the apiKey needs to be a valid key from google. Below are the steps to obtain a valid key.

- Obtaining a Maps API Key

- Edit the main.xml and replace apiKey with the value from google. See below on how to generate an MD5 Certificate fingerprint.



5. Now clean, build and compile again. Click the UP/Down to ZOOM, and LEFT/RIGHT to switch Map View and Satellite View.






6. Now that I got Map View to work, maybe I'll create something useful next time.

Source Code

Sunday, October 19, 2008

Android Simple SQLiteDatabase

Account List
New Account

Modify Account


Source Code can be downloaded below. Also, the syntaxhighlighter for the XML code, has some problems. For some reason, the JS and CSS I used modifies the XML I place. Just check with the code downloadable below for the correct xml files.



New Classes Used:
ListActivity
SQLiteOpenHelper

1. Create new Android Project.
-Activity Name: AccountList

2. Create View Classes and XML

2.1 Create account_list.xml








account_row.xml




2.2. Create ListActivity class AccountList

public class AccountList extends ListActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_list);
initComponents();
}
private void initComponents() {
}
}

2.3 Create account_detail.xml
Below is an example of combining two or more layouts.



















2.4. Create Activity class AccountDetail

public class AccountDetail extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_detail);
initComponents();
}
private void initComponents() {
}
}
3. Create Model Classes
3.1 Create Account class

public class Account {
public static final String COL_ROW_ID = "_id";
public static final String COL_SERVER_NAME = "server_name";
public static final String COL_USERNAME = "username";
public static final String COL_PASSWORD = "password";
public static final String SQL_TABLE_NAME = "account_db";
public static final String SQL_CREATE_TABLE = "CREATE TABLE "
+ SQL_TABLE_NAME + " "
+ " (_id integer primary key autoincrement, "
+ "server_name text not null, username text not null, "
+ "password text not null); ";

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getServerName() {
return serverName;
}

public void setServerName(String serverName) {
this.serverName = serverName;
}

private String username;

private String password;

private String serverName;

private int id;
}

3.2 Create ActiveRecord interface
This is the interface that will be extend by POJO to make it a sort-of Active Domain. I'm not really experienced with the Active Domain pattern but this is my understanding of it. So just correct me if I am wrong and I'll change the code.

public interface ActiveRecord {
public long save();
public boolean delete();
public void load(Activity activity);
public Cursor retrieveAll();
public void setSQLiteDatabase(SQLiteDatabase sqliteDb);
}

3.3 Extend SQLiteOpenHelper
I derived MyDatabaseAdapter class from
Tutorial: A Notepad Application, However I made the MyDatabaseAdapter as a utility class instead of placing CRUD functions inside it. I made the Controller a sort-of Active Domain pattern so that the MyDatabaseAdapter can be re-used for future applications.

public class MyDatabaseAdapter extends SQLiteOpenHelper {
private static SQLiteDatabase sqliteDb;
private static MyDatabaseAdapter instance;

private static final String DATABASE_NAME = "simple_sqlite_db";
private static final int DATABASE_VERSION = 1;


private MyDatabaseAdapter(Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(Account.SQL_CREATE_TABLE);
}

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

private static void initialize(Context context) {
if(instance == null) {
instance = new MyDatabaseAdapter(context, DATABASE_NAME, null, DATABASE_VERSION);
sqliteDb = instance.getWritableDatabase();
}
}

public static final MyDatabaseAdapter getInstance(Context context) {
initialize(context);
return instance;
}

public SQLiteDatabase getDatabase() {
return sqliteDb;
}

public void close() {
if(instance != null ) {
instance.close();
instance = null;
}
}
}

3.4 Create implementation of ActiveRecord on Account class
Here's the code that implements the CRUD methods of the ActiveRecord interface. I also learned that from the Notepad Tutorial, since the Android Docs, does not really explain how to use SQLiteDB, or maybe I just do not know where to look.

public class Account implements ActiveRecord {

public static final String COL_ROW_ID = "_id";
public static final String COL_SERVER_NAME = "server_name";
public static final String COL_USERNAME = "username";
public static final String COL_PASSWORD = "password";

public static final String SQL_TABLE_NAME = "account_db";
public static final String SQL_CREATE_TABLE = "CREATE TABLE "
+ SQL_TABLE_NAME + " "
+ " (_id integer primary key autoincrement, "
+ "server_name text not null, username text not null, "
+ "password text not null); ";

private SQLiteDatabase sqliteDatabase;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getServerName() {
return serverName;
}

public void setServerName(String serverName) {
this.serverName = serverName;
}

private String username;

private String password;

private String serverName;

private int id;

@Override
public void load(Activity activity) {
Cursor cursor = sqliteDatabase.query(true, SQL_TABLE_NAME,
new String[] { COL_ROW_ID, COL_SERVER_NAME, COL_USERNAME,
COL_PASSWORD }, COL_ROW_ID + "=" + id, null, null,
null, null, null);
if (cursor != null) {
cursor.moveToFirst();
activity.startManagingCursor(cursor);
setId(cursor.getInt(cursor
.getColumnIndex(COL_ROW_ID)));
setPassword(cursor.getString(cursor
.getColumnIndex(COL_PASSWORD)));
setUsername(cursor.getString(cursor
.getColumnIndex(COL_USERNAME)));
setServerName(cursor.getString(cursor
.getColumnIndex(COL_SERVER_NAME)));
}
}

@Override
public Cursor retrieveAll() {
return sqliteDatabase.query(SQL_TABLE_NAME, new String[] { COL_ROW_ID,
COL_SERVER_NAME, COL_USERNAME, COL_PASSWORD }, null, null,
null, null, null);
}

@Override
public long save() {
ContentValues values = new ContentValues();
if (id <= 0) { values.put(COL_SERVER_NAME, serverName); values.put(COL_USERNAME, username); values.put(COL_PASSWORD, password); return sqliteDatabase.insert(SQL_TABLE_NAME, null, values); } else { values.put(COL_SERVER_NAME, serverName); values.put(COL_USERNAME, username); values.put(COL_PASSWORD, password); return sqliteDatabase.update(SQL_TABLE_NAME, values, COL_ROW_ID + "=" + id, null); } } public boolean delete() { return sqliteDatabase.delete(SQL_TABLE_NAME, COL_ROW_ID + "=" + id, null) > 0;
}

@Override
public void setSQLiteDatabase(SQLiteDatabase sqliteDatabase) {
this.sqliteDatabase = sqliteDatabase;
}
}

4. Create Controllers
4.1 AccountList Controllers
For the AccountList I used the OptionsMenu.

public class AccountList extends ListActivity {
/** Called when the activity is first created. */
private MyDatabaseAdapter myDatabaseAdapter;

private static final int INTENT_NEXT_SCREEN = 0;
public static final String INTENT_EXTRA_SELECTED_ROW = "SELECTED_ROW";

private static final int INSERT_ID = Menu.FIRST;
private static final int DELETE_ID = Menu.FIRST + 1;
private static final int EXIT_ID = DELETE_ID + 1;
private Account account = new Account();
private Intent intent;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_list);
myDatabaseAdapter = MyDatabaseAdapter.getInstance(this);
intent = new Intent(this, AccountDetail.class);
initComponents();
}

private void initComponents() {
account.setSQLiteDatabase(myDatabaseAdapter.getDatabase());
Cursor recordsCursor = account.retrieveAll();
startManagingCursor(recordsCursor);
String[] from = new String[] { Account.COL_SERVER_NAME };
int[] to = new int[] { R.id.tfServerName };
SimpleCursorAdapter records = new SimpleCursorAdapter(this,
R.layout.account_row, recordsCursor, from, to);
setListAdapter(records);
}

@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch (item.getItemId()) {
case INSERT_ID:
createRecord();
return true;
case DELETE_ID:
account.setId((int) getListView().getSelectedItemId());
account.delete();
initComponents();
return true;
case EXIT_ID:
finish();
}
return super.onMenuItemSelected(featureId, item);
}

private void createRecord() {
intent.putExtra(INTENT_EXTRA_SELECTED_ROW, 0);
startActivityForResult(intent, INTENT_NEXT_SCREEN);
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Log.v(getClass().getSimpleName(), "id=" + id);
intent.putExtra(INTENT_EXTRA_SELECTED_ROW, id);
startActivityForResult(intent, INTENT_NEXT_SCREEN);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, 0, R.string.btnAdd);
menu.add(0, DELETE_ID, 0, R.string.btnDelete);
menu.add(0, EXIT_ID, 0, R.string.btnExit);
return true;
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
initComponents();
}
}
4.2 AccountDetail Controllers

public class AccountDetail extends Activity implements OnClickListener {

private MyDatabaseAdapter myDatabaseAdapter;
private long selectedRow;
private TextView tvId;
private EditText etServerName, etUserName, etPassword;
private Button btnSave, btnCancel;
private Account account;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_detail);
myDatabaseAdapter = MyDatabaseAdapter.getInstance(this);
initComponents();
}

private void initComponents() {
selectedRow = getIntent().getLongExtra(
AccountList.INTENT_EXTRA_SELECTED_ROW, 0);
tvId = (TextView) findViewById(R.id.tvId);
etServerName = (EditText) findViewById(R.id.etServerName);
etUserName = (EditText) findViewById(R.id.etUsername);
etPassword = (EditText) findViewById(R.id.etPassword);

account = new Account();
account.setSQLiteDatabase(myDatabaseAdapter.getDatabase());
Log.v(getClass().getSimpleName(), "selectedRow=" + selectedRow);
account.setId((int) selectedRow);
if (selectedRow > 0) {
account.load(this);
}
Log.v(getClass().getSimpleName(), "account.getId()=" + account.getId());
if (account.getId() > 0) {
tvId.setText(account.getId() + "");
etServerName.setText(account.getServerName());
etUserName.setText(account.getUsername());
etPassword.setText(account.getPassword());
} else {
tvId.setText("new");
}
btnSave = (Button) findViewById(R.id.btnSave);
btnSave.setOnClickListener(this);
btnCancel = (Button) findViewById(R.id.btnCancel);
btnCancel.setOnClickListener(this);
}


Source Code
If you find something confusing from this tutorial, just post a comment, and I'll fix it for you.


In order to display multiple items on the list, just edit account_row.xml as shown below.


android:layout_width="fill_parent" android:layout_height="wrap_content">
android:layout_width="wrap_content" android:layout_alignParentRight="false"
android:layout_height="wrap_content" android:text="Server Name" />
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_toRightOf="@id/tfServerName"
android:text="Username"/>



Also modify AccountList.java to display the fields you want.


String[] from = new String[] { Account.COL_SERVER_NAME, Account.COL_USERNAME};
int[] to = new int[] { R.id.tfServerName, R.id.tfUsername };



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

Java Source Code Style

I just found out how to write Java and XML code with syntax highlights in blogger.
You can find the project here. So on the next post, i'll use it instead of screen captures of the source code.
public class HelloWorld {
private String message = "Hello World";

public static void main(String[] args) {
System.out.println(message);
}
}

Sunday, September 28, 2008

Hello World Android version

For J2ME developers, read the J2ME version first.

Link for the source code at the end of the tutorial.

Step 1. Create Android Project By File > New then select Android Project.

Step 2. Enter Project Name, to be consistent with the J2ME version, I chose HelloWorld.

Android enforces a two identifier package, thus javapadawan.android is used.
The Activity name, entered in above will serve as the entry point of the application. This is similar to J2ME's MIDlet. One can create multiple MIDlets, but only once can be used as a program entry point.

Step 3. Create HelloWorldActivity.java.


Note that R.java is generated by the Eclipse Android plug-in, so don't bother with that. Also, when encountering compile problems with R.java, just do a Clean on the Eclipse Menu Project>Clean then Build the Project.

Step 4. Creating the User Interface.

When you created the HelloWorldActivity, the Eclipse Android plug-in already created the layout XML.


Above is generated XML. Although, one can program Android user-interfaces by hand, without using XML, but that's considered an advanced topic. Similar to coding UIs using MIDP's Canvas.


Below is the User Interface similar to the J2ME version of the HelloWorld. The one's encircled in red are going to be used in the HelloWorldActivity class, the @+id/ should be unique. The usage of such will be shown below. Further, detailed explaination for the xml layouts will come in the future tutorials.



Above is the result of the xml layout above. This uses the RelativeLayout.

Step 5. Coding the HelloWorldActivity.

As much as possible, I want the Android version to be similar to the J2ME version of the HelloWorld so that a J2ME developer may see the differences.


Thus, I also included a initComponents() method. This is where the User Interface Components defined in the XML are retrieved.

Notice the usage of the R.java. Whenever, there are thing modified in the XMLs such as the main.xml and string.xml, it gets reflected here.

The android:id identified above will become R.id..

So to be able to use the User Interface components, it need to be retrieved using the Activity method, findByViewId(...). The parameter needed by findByViewId is integer, but the R.java values should be used.

So just set the values of the findById(...) to a private variable inside the class. For the buttons, (similar to MIDP), listeners must be assigned. Since I used the same class as the listener in J2ME, I will also use the same for Android.

So just, add implements OnClickListener to the class and implement its method.


Similar to J2ME's commandAction(Command c, Displayable d), when a button is clicked, the View (Component) responsible to the event is also passed in the onClick(View v) method.

So above is the implementation I did. Notice the similarities of the MIDP version.

However, the Alert is quite different and more complex in android. Just see the AlertBuilder API.


Step 6. Clean, Build and Run


Clean and Buld the Project.

Run by going to the Menu then Run > Run (Ctrl + F11).
Then Run As Android Applicaiton. On the first Run, this may take a couple of minutes depending on your Hardware.





Step 7. Debugging and Cleaning the Emulator.
Unlike in Netbeans IDE, Mobility where the System.out.print, and stacktraces displays on the IDE's consolde

In Eclipse Android Plug-in, you need to go to the DDMS perspective to debug. Open a new Perspective Window > Open Perspective > Other


On the dialog, select DDMS.


Here's what the DDMS Perspective looks like. Also, you may delete the installed apps if you want. Highlight the .apk to be deleted and click the red (-) minus button shown.



Source Code

Hello World J2ME version

Before reading this, you should have downloaded Netbeans IDE 6.1, choose the Mobility version or the All version.

When creating a J2ME application, I usually separate my MIDlet class, view-controller classes and model classes.

I do not separate View and Controller since normally my views only have a couple of controls and I find it more organized that way.

Link for the source code at the end of the tutorial.

Step 1. Create a new Project By File>New Project
Click Mobility from the Categories and MIDP Application from Projects.


Step 2. Write any Project Name and select a project location then click the Next Button.
Step 3. Select CLDC 2.0 and MIDP 2.0

Step 4. Click Finish. Delete all generated .java files as we will not be using the WYSIWYG editor.

Then create two classes, HelloWorldForm.java and HelloWorldMIDlet.java
You should have the following files on your Project Tab.


Step5. Writing the HelloWorldMIDlet.java code.

PauseApp, destroyApp and startApp are the abstract classes of the MIDlet class that needs to be implemented. I added two methods to be used by the UI class, showScreen(Displayble d) and showScreenAndAlert(Alert a, Displayble d).

The code in startApp() will be discussed after the next step.


Step 6. Coding the HelloWorldForm.java.

As you may have noticed, the MIDlet class is passed to the constructor, I find this useful in creating a apps with a lot of forms. Further, the MIDlet class may also be used to store Application sessions data, also handling unexpected application problems.

The initComponents() method is where the UI Components are initialized and added to the Form.

If you do not want the controller and the view to be on the same class, you may create, or pass in the constructor a class that implements the CommandListener interface.

On the code, instead of setCommandListener(this), just replace this with a CommandListener instance.



Step 7. Building and Running.





Source Code

About.

I am a Java Mobile Edition developer ever since MIDP 1.0, I've already experienced and done the workarounds of almost all of the limitation of the MIDP platform. The worst ever is not being able to get the Mobile Number of the Phones (This is for most Phones, some implementations offer a native utility for retrieving IMEI, Numbers, Contacts but not the standard MIDP API).

When I first reviewed Google Android, I got excited with the fact that everything in the phones is programmable and is not limited to a J2ME like tiny little sandbox. I worry about the Device's security though.

I'll be starting a Journal of how to program Android from a J2ME Developer's Perspective. I am planning to use my knowledge in MIDP, and try to figure out how to port it in android. In the future, I may start on some advanced Android apps and see what the android can really do.

Email

java.padawan@androidph.com