Sunday, January 29, 2012

Development of Android application using native Android, jQ.Mobi and HTML



Introduction


In this tutorial we'll develope a simple Andorid application using combination of native Android development, jQ.Mobi framework and HTML. Application sample can be downloaded from this link.

The main point of this tutorial is too use adventages of HTML and JavaScript for UI design, and native Android development and Java language for business logic development. For UI development, we'll use jQ.Mobi framework and HTML. JQ.Mobi is an HTML5 optimized rewriting JQuery JavaScript library, which offers way much faster execution than some other JS frameworks (JQuery Mobile, Zepto...). It is compatible for Andorid and iPhone platforms.

The basic concept of this sample lies in integration of HTML/JavaScript based UI with Android Java backend. Andorid provides a generic way of exposiong Android Java objects in JavaScript code through interface that is registered using WebView class. This interface provide us a set of abilities:

1. accessing Java methods from JavaScript
2. accessing HTML/JavaScript from Java code
3. parameter data types in method calls between Java-JavaScript

Screen flow


This very simple aplication will contain two HTML pages. First page will be used for saving fruit data in Andorid SQLite database and the second page will show all fruits from database table.

First page (index.html) - Add new fruit
Second page (viewall.html) - View all added fruits

Database access


First, create a new Andorid project in Eclipse. After that, create a new package where we'll add some classes for database access. Create a new class named DataSQLHelper that will extend SQLiteOpenHelper class.

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import android.util.Log;

public class DataSQLHelper extends SQLiteOpenHelper {
 
 public static final String DB_NAME = "fruitapp.db";
 public static final int DB_VERSION = 1;
 public static final String TABLE_FRUIT = "fruit";
 //columns
 public static final String FRUIT_NAME = "fruit_name";
 public static final String FRUIT_NUMBER = "fruit_number";
  
 public DataSQLHelper(Context context) {
  super(context, DB_NAME, null, DB_VERSION);
 }

 @Override
 public void onCreate(SQLiteDatabase db) {
  String sql = "CREATE TABLE " + TABLE_FRUIT +" ( " 
  +BaseColumns._ID+" integer primary key autoincrement, "+
  FRUIT_NAME+" text, "+
  FRUIT_NUMBER+" int)";
  Log.d("FruitData","onCreate: "+sql);
  db.execSQL(sql);
 }

 @Override
 public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
  // TODO Auto-generated method stub

 }
}

After creating first instance of this class, SQLite will create new database called "fruitapp.db". Method onCreate(SQLiteDatabase db) will add table "fruit" to previosly created database.

FruitEntity class is used as a representation of database table.

public class FruitEntity {
 private long id;
 private String name;
 private int number;
 
 public long getId() {
  return id;
 }
 public void setId(long id) {
  this.id = id;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public int getNumber() {
  return number;
 }
 public void setNumber(int number) {
  this.number = number;
 }
}

Another class called DBRepository contains methods for inserting fruit data in "fruit" database table - insertFruit(String dbtable, Context context, FruitEntity fruit). It also contains method getFruits(String dbtable, Context context) which returns JSON String representation with all records from "fruit" table. For purpose of parsing JSON objects in this sample was used Jackson JSON processor.


import java.io.StringWriter;
import java.util.ArrayList;
import org.codehaus.jackson.map.ObjectMapper;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import org.fruit.db.FruitEntity;

import org.fruit.db.DataSQLHelper;

public class DBRepository{
 
 DataSQLHelper sqlHelper;
 
 public DBRepository(Context context){
  this.sqlHelper = new DataSQLHelper(context);
 }
 
 //returns Cursor for reading reocrds from database table
 public Cursor getCursor(String dbtable, Context context){
  SQLiteDatabase db = sqlHelper.getReadableDatabase();
  Cursor cursor = db.query(dbtable,null,null,null,null,null, null);

  return cursor;
 }
 
 public String getFruits(String dbtable, Context context){
  Cursor cursor = getCursor(dbtable, context);
  StringWriter ret = new StringWriter();
  ArrayList fruits = new ArrayList();
  while(cursor.moveToNext()){
   FruitEntity t = new FruitEntity();
   t.setId(cursor.getLong(0));
   t.setName(cursor.getString(1));
   t.setNumber(cursor.getInt(2));
   fruits.add(t);
  }
  try{
   //using jackson mapper for generating JSON object
   ObjectMapper mapper = new ObjectMapper();
   mapper.writeValue(ret, fruits);
  }catch(Exception e){
   e.printStackTrace();
  }
  closeDB();
  return ret.toString();
 }
 
 public void insertFruit(String dbtable, Context context, FruitEntity fruit){
  SQLiteDatabase db = sqlHelper.getWritableDatabase();
  ContentValues values = new ContentValues();
  values.put(DataSQLHelper.FRUIT_NAME, fruit.getName());
  values.put(DataSQLHelper.FRUIT_NUMBER, fruit.getNumber());
  db.insert(DataSQLHelper.TABLE_FRUIT, null, values);
  
  closeDB();
 }
 
 public void closeDB(){
  sqlHelper.close();
 }
}


Main Activity class


After creating new Project in Eclipse, go to your main Activity class and add this code:
import org.fruit.db.DBRepository;
import org.fruit.db.DataSQLHelper;
import org.fruit.db.FruitEntity;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
import android.webkit.WebView;

public class FruitAppActivity extends Activity {
 WebView webView;
 DBRepository dbRepository;
 private Handler handler = null;
 private static final String HTML_ROOT = "file:///android_asset/www/"; 
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
        webView = new WebView(this);
       
        setContentView(webView);
        webView.getSettings().setJavaScriptEnabled(true);    
        dbRepository = new DBRepository(this);
        
        handler = new Handler();  //creates handler instance for page loading 
        webView.addJavascriptInterface(this, "fruitInterface");        
        loadPage("index.html");
    }
 
 public void loadPage(String in){
     final String url = HTML_ROOT + in;
     loadURL(url);
    }
    
 //Loads pages and executes JavaScript. 
    private void loadURL(final String in){
     handler.post(new Runnable() {
            public void run() {
             webView.loadUrl(in);
            }
        });
    }
}

In onCreate method we create new WebView component that will be used for HTML preview and JavaScript execution. We'll also need to enable JavaScript for WebView component. In the next step, we'll create an instance of BDRepository class. Creating this instance, it will be created fruitapp.db database with "fruit" table. After that, we'll register "fruitInterface" for current context. This JavaScript interface represents the interface that will be used for calling Java methods from JavaScript, and vice versa. In the end, we'll load index page.

Add additonal methods to main Activity class:

 public void addNewFruit(String fruit_name, String fruit_num){
  FruitEntity fruit = new FruitEntity();
  fruit.setName(fruit_name);
  fruit.setNumber(Integer.parseInt(fruit_num));
  
  dbRepository.insertFruit(DataSQLHelper.TABLE_FRUIT, this, fruit);
 }
 
 public void sendResultToJavaScript(String callback){
  String result = dbRepository.getFruits(DataSQLHelper.TABLE_FRUIT,this);
  final String callbackFunction = "javascript:" + callback + "('" + result + "')";
  loadURL(callbackFunction); 
 }
 
 @Override
 public boolean onKeyDown(int keyCode, KeyEvent event){
  if((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()){
   webView.goBack();
   return true;
  }
  return super.onKeyDown(keyCode, event);
 }

Method addNewFruit is used to insert data from HTML form into database table. On the other hand, method sendResultToJavaScript is used to read all the records from "fruit" table in form of JSON representation and send JSON string to HTML by sending it like argument of javascript function. Both of these methods will be called from JavaScript using JavaScript interface.

Override method onKeyDown to define event on click of back button. In our case, default behavior is to go to previuos page.

Adding HTML pages and JavaScript


In assets create folders www and css-js:

In folder css-js add exported files from downloaded jQ.Mobi archive.

In folder www create new "index.html" page. In this case, it was used HTML 5, but you can use any version of HTML.


 
 
 
 

 
 

In "head" tag we add css and jQ.Mobi libraries. After that, create simple form with basic input fields and buttons. In the end, add JavaScript functions.

  • saveFruit function reads values from input fields and use it as arguments in Java method addNewFruit call. Notice that for Java method calls, we use interface that we added before in our Activity class.
  • goToAllRecordsPage function redirects to next page.
  • clearFields function clears values from input fields.

In folder www create another HTML page called "viewall.html".

 
 
 
 

 
 

Again, in the "head" tag we add css and jQ.Mobi libraries. In body part, add an unordered list element that will be used to display fruit data.

After page loads, Java sendResultToJavaScript method will be called. As argument of this method we use name of JavaScript method that we'll be invoked. JavaScript function getRecords we'll parse JSON result set and add records data to list items.