Thursday, June 30, 2011

Address Panel Post 3: Database classes Class (java)

In this post I will show you how to set up the classes you need to save the values in the address panel to a SQLite database.

First you will need to define the schema of the database.

Schema.java
package ewe.custom.controls.database;

public class Schema {
package ewe.custom.controls.database;

public class Schema {


 //Database Info
 //Database name
 public static final String DATABASE_NAME="pvtdb";
 //increment this anytime the structure is changed
 public static final int DATABASE_VERSION=1;  
 
 //address table name
 public static final String ADDRESS_TABLE="address";
 //address column names
 public static final String ADDRESS_ADDR1="ad_addr1";
 public static final String ADDRESS_ADDR2="ad_addr2";
 public static final String ADDRESS_CITY="ad_city";
 public static final String ADDRESS_STATE="ad_state";
 public static final String ADDRESS_ZIP="ad_zip";
 
}


Next add your database helper class. This class will be responsible for creating and updating the database.

DbHelper.java
package ewe.custom.controls.database;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class DbHelper extends SQLiteOpenHelper {

 //This string creates the table
 public static final String CREATE_ADDRESS_TABLE = "create table "
   + Schema.ADDRESS_TABLE + " ( " + Schema.ADDRESS_ADDR1
   + " text not null, " + Schema.ADDRESS_ADDR2 + " text not null, "
   + Schema.ADDRESS_CITY + " text not null, " + Schema.ADDRESS_STATE
   + " text not null, " + Schema.ADDRESS_ZIP + " text not null "
   + ") ;";

 //this is the constructor
 public DbHelper(Context context, String name, CursorFactory factory,
   int version) {
  super(context, name, factory, version);
 }

 //called when database is first created
 //usually when app is installed or user clears app data
 @Override
 public void onCreate(SQLiteDatabase arg0) {
  arg0.execSQL(CREATE_ADDRESS_TABLE);
 }

 //called when you update the database version
 //look at Schema.java
 @Override
 public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
  arg0.execSQL("drop table if exists " + Schema.ADDRESS_TABLE);
  onCreate(arg0);
 }

}




Finally you need to create the class that is going to contain all your database methods. The methods in this class will insert values and load values from the database. The methods are called from RelativeLayout.java file in blog post 2.

MyDb.java
package ewe.custom.controls.database;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Bundle;
import android.util.Log;

/**
 * @author androidcontrols.blogspot.com
 * Database methods
 */
public final class MyDb {

 //database object
 private SQLiteDatabase db;
 private final Context context;
 private final DbHelper dbhelper;
 private static final String TAG = "db:";

 /** Constructor that sets the context
  *  will create database if it doesn't exist
  * @param c
  */
 public MyDb(Context c) {
  context = c;
  dbhelper = new DbHelper(context, Schema.DATABASE_NAME, null,
    Schema.DATABASE_VERSION);
 }

 /**
  * Opens database
  * @throws SQLiteException
  */
 public void open() throws SQLiteException {
  try {
   db = dbhelper.getWritableDatabase();
  } catch (SQLiteException ex) {
   Log.v(" Open database exception caught", ex.getMessage());
   db = dbhelper.getReadableDatabase();
  }
 }

 /**
  * Closes database
  */
 public void close() {
  db.close();
 }

 /**
  * @author androidcontrols.blogspot.com
  * Inserts the address into the table
  */
 public long insertAddress(String addr1, String addr2, String city,
   String state, String zip) {
  try {
   ContentValues newAddrRow = new ContentValues();
   newAddrRow.put(Schema.ADDRESS_ADDR1, addr1);
   newAddrRow.put(Schema.ADDRESS_ADDR2, addr2);
   newAddrRow.put(Schema.ADDRESS_CITY, city);
   newAddrRow.put(Schema.ADDRESS_STATE, state);
   newAddrRow.put(Schema.ADDRESS_ZIP, zip);
   return db.insert(Schema.ADDRESS_TABLE, null, newAddrRow);
  } catch (SQLiteException ex) {
   Log.v(TAG, "Insert address failed: " + ex.getMessage());
   return -1;
  }
 }

 /**
  * @author androidcontrols.blogspot.com
  * @return Bundle containing user address
  */
 public Bundle getAddressBundle() {
  Cursor c;
  Bundle b = new Bundle();
  try {
   c = db.rawQuery("SELECT " + Schema.ADDRESS_ADDR1 + ", "
     + Schema.ADDRESS_ADDR2 + ", " + Schema.ADDRESS_CITY + ", "
     + Schema.ADDRESS_STATE + ", " + Schema.ADDRESS_ZIP + " "
     + "FROM " + Schema.ADDRESS_TABLE, new String[] {});
   if (c.getCount() > 0) {
    c.moveToFirst();
    b.putString("addr1", c.getString(0));
    b.putString("addr2", c.getString(1));
    b.putString("city", c.getString(2));
    b.putString("state", c.getString(3));
    b.putString("zip", c.getString(4));
   } else {
    // TODO return invoice not found in database (should never
    // happen)
    return null;
   }
   return b;
  } catch (Exception ex) {
   Log.v(TAG, "Failed to get invoice header info: " + ex.getMessage());
   return null;
  }
 }

 /**
  * @author androidcontrols.blogspot.com
  * Deletes the old address then inserts new values
  */
 public long saveAddress(String addr1, String addr2, String city,
   String state, String zip) {
  this.deleteOldAddress();
  return this.insertAddress(addr1, addr2, city, state, zip);
 }

 /**
  * @author androidcontrols.blogspot.com
  * Delete old address
  */
 public long deleteOldAddress() {
  try {
   return db.delete(Schema.ADDRESS_TABLE, "", new String[] {});
  } catch (Exception ex) {
   return -1;
  }
 }
}

Address Panel Post 2: RelativeLayout Class (java)

In post 1 I showed you the XML files that define the layout for the control. In this post I am going to show you the java file behind the layout. It's named UserAddress.java. UserAddress is a subclass of RelativeLayout. It is responsible for loading all the data from the database when the form is entered. It also contains the code to save the values to the database.


package ewe.custom.controls.address;

import ewe.custom.controls.R;
import ewe.custom.controls.database.MyDb;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.Spinner;

/**
 * @author - http://droidcontrols.blogspot.com
 * This is a view that contains the standard fields for address entry.  
 */
/**
 * @author http://droidcontrols.blogspot.com
 *
 */
public class UserAddress extends RelativeLayout {
 Button btnSave; //save button
 EditText addr1; //address line one input
 EditText addr2; //address line two input
 EditText city; //city input
 Spinner state; // state input
 EditText zip; // zip code input
 MyDb mdb; //TODO change to your database if you don't want to use the one i provided in blog 3

 //list of states
 private static final String[] states = { "Alabama", "Alaska", "Arizona",
   "Arkansas", "California", "Colorado", "Connecticut", "Delaware",
   "Distric of Columbia", "Florida", "Georgia", "Hawaii", "Idaho",
   "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana",
   "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota",
   "Mississippi", "Missouri", "Montana", "Nevada", "New Hampshire",
   "New Jersey", "New Mexico", "New York", "North Carolina",
   "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania",
   "Rhode Island", "South Carolina", "South Dakota", "Tennessee",
   "Texas", "Utah", "Vermont", "Virginia", "Washington", "Wisconsin",
   "Wyoming" };

 /**
  * constructor #1
  * @param context
  */
 public UserAddress(Context context) {
  super(context);
  // First constructor
  inflateLayout(context);
  initializeControls(context);
  loadValuesFromDatabase(context);
 }

 /**
  * constructor #2
  * @param context
  * @param attrs
  */
 public UserAddress(Context context, AttributeSet attrs) {
  super(context, attrs);
  //Second constructor - used most often
  inflateLayout(context);
  initializeControls(context);
  loadValuesFromDatabase(context);
 }

 /**
  * constructor #3
  * @param context
  * @param attrs
  * @param defStyle
  */
 public UserAddress(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  // Third constructor
  inflateLayout(context);
  initializeControls(context);
  loadValuesFromDatabase(context);
 }

 /**
  * Inflates the main xml file addressform.xml (See blog post #1)
  * @param context
  */
 private void inflateLayout(Context context) {
  LayoutInflater vi = (LayoutInflater) context
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  //inflate addressform.xml
  View v = vi.inflate(R.layout.addressform, this);

 }

 /**
  * This method will load associate the view objects with the views in the xml file. 
  * It also populates the spinner.
  */
 private void initializeControls(Context context) {
  //point to the views in the xml file
  addr1 = (EditText) findViewById(R.id.et_addr1);
  addr2 = (EditText) findViewById(R.id.et_addr2);
  city = (EditText) findViewById(R.id.et_city);
  state = (Spinner) findViewById(R.id.sp_state);
  zip = (EditText) findViewById(R.id.et_zip);
  btnSave = (Button) findViewById(R.id.btn_addr_save);

  //set the array adapter to the spinner
  //will display the dates in the String[] states
  //the spinner_entry.xml is an xml file whose root element is a single textview
  ArrayAdapter<String> mySpinnerAdapter = new ArrayAdapter<String>(
    context, R.layout.spinner_entry, states);
  state.setAdapter(mySpinnerAdapter);

  //what to do when the save button is clicked
  btnSave.setOnClickListener(new OnClickListener() {

   @Override
   public void onClick(View arg0) {
    //get the index of the item selected in the dropdown
    String lsstate = Integer.toString(state
      .getSelectedItemPosition());
    //TODO implement how your own database saves the information
    mdb.saveAddress(addr1.getText().toString().trim(), addr2
      .getText().toString().trim(), city.getText().toString()
      .trim(), lsstate, zip.getText().toString().trim());
   }

  });
 }

 /**
  * @param context from activity that contains the view
  * 
  * This will load the data from the database if the address was previously saved
  */
 private void loadValuesFromDatabase(Context context) {

  //TODO implement using your applications SQL database
  mdb = new MyDb(context);
  mdb.open();

  //if you don't include this if statement the code may crash when 
  //in the designer/graphical view of the xmlfile
  if (!this.isInEditMode()) {

   //this is a method defined in our database used to get the bundle(hashtable) containing the user's 

saved address
   Bundle bAddr = mdb.getAddressBundle();

   if (bAddr != null) {
    if (bAddr.getString("addr1") != null) {
     //sets the value of the EditText containing address line 1
     addr1.setText(bAddr.getString("addr1"));
    }
    if (bAddr.getString("addr2") != null) {
     addr2.setText(bAddr.getString("addr2"));
    }
    if (bAddr.getString("city") != null) {
     city.setText(bAddr.getString("city"));
    }
    if (bAddr.getString("state") != null) {
     //sets the value of the City Spinner 
     state.setSelection(Integer.parseInt(bAddr
       .getString("state")));
    }
    if (bAddr.getString("zip") != null) {
     zip.setText(bAddr.getString("zip"));
    }
   }

  }

 }
}



If you have your own database then you will need to create a method to retrieve and save the address. You can use the methods I have written in the next post. If you don't have a database set up just use the one I provide in post 3. #TODO add links

Monday, June 27, 2011

Address Panel Post 1: Layout (xml)

This post will show the two xml files needed for the address panel. Both of these files should be added to the layout folder of your android project.


Image: Layout of the address panel

The panel consists of the following views:
  • 5 TextViews to display the label for each field
  • 4 EditTexts to store the address line 1, address line 2, city, and zip code
  • 1 Spinner to store the state
  • 1 Button to save the values to the database
The root element of the panel is a RelativeLayout.  It is stored in a file called addressform.xml.  The second post of this series will show you how to create the java file behind this xml file.



addressform.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:text="Address Line 1"
        android:layout_alignParentTop="true"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:layout_marginLeft="20dip"
        android:layout_marginTop="10dip"
        android:id="@+id/lbl_addr1"></TextView>
    <EditText
        android:layout_width="fill_parent"
        android:layout_below="@+id/lbl_addr1"
        android:id="@+id/et_addr1"
        android:layout_height="wrap_content"
        android:hint="Address Line 1"
        android:inputType="text"
        android:layout_marginLeft="30dip"
        android:layout_marginRight="30dip"
        android:maxLines="1"
        android:textSize="18sp"></EditText>
    <TextView
        android:layout_width="wrap_content"
        android:text="Address Line 2"
        android:layout_below="@+id/et_addr1"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:layout_marginLeft="20dip"
        android:layout_marginTop="5dip"
        android:id="@+id/lbl_addr2"></TextView>
    <EditText
        android:layout_width="fill_parent"
        android:layout_below="@+id/lbl_addr2"
        android:id="@+id/et_addr2"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:layout_marginLeft="30dip"
        android:layout_marginRight="30dip"
        android:maxLines="1"
        android:textSize="18sp"
        android:hint="Address Line 2"></EditText>
    <TextView
        android:layout_width="wrap_content"
        android:text="City"
        android:layout_below="@+id/et_addr2"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:layout_marginLeft="20dip"
        android:layout_marginTop="5dip"
        android:id="@+id/lbl_city"></TextView>
    <EditText
        android:layout_width="fill_parent"
        android:inputType="text"
        android:layout_marginRight="30dip"
        android:layout_marginLeft="30dip"
        android:id="@+id/et_city"
        android:hint="City"
        android:maxLines="1"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:layout_below="@+id/lbl_city"></EditText>

    <TableLayout
        android:layout_width="fill_parent"
        android:id="@+id/tableLayout1"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dip"
        android:layout_below="@+id/et_city"
        android:layout_marginTop="5dip">
        <TableRow
            android:id="@+id/tableRow1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
            <TextView
                android:text="State"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="16sp"
                android:id="@+id/lbl_state"
                android:layout_weight=".5"></TextView>
            <TextView
                android:layout_width="wrap_content"
                android:text="Zip Code"
                android:layout_marginLeft="10dip"
                android:layout_height="wrap_content"
                android:textSize="16sp"
                android:id="@+id/lbl_zip"
                android:layout_weight=".5"></TextView>
        </TableRow>
        <TableRow
            android:id="@+id/tableRow2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
            <Spinner
                android:layout_height="50dip"
                android:layout_width="80dip"
                android:maxWidth="80dip"
                android:id="@+id/sp_state"
                android:layout_marginLeft="10dip"
                android:layout_weight=".5"></Spinner>
            <EditText
                android:layout_weight=".5"
                android:layout_marginLeft="20dip"
                android:hint="Zip Code"
                android:id="@+id/et_zip"
                android:maxLines="1"
                android:inputType="text"
                android:layout_marginRight="30dip">
            </EditText>
        </TableRow>
    </TableLayout>

    <Button
        android:layout_width="wrap_content"
        android:id="@+id/btn_addr_save"
        android:text="Save"
        android:textSize="18sp"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:minWidth="100dip"
        android:layout_marginBottom="10dip"></Button>
</RelativeLayout>




The only other xml file you need is one to set the layout for the items in your spinner. The layout will consist of a single textview element. Name this file spinner_entry.xml.

spinner_entry.xml

<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<TextView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="match_parent"
  android:textSize="25dip"
  android:textColor="#000"
  android:gravity="center">
</TextView>



That's it! You've added all the xml you need for your project. Move onto step two.//TODO add link

Address Panel - Summary

In the next several posts I am going to discuss how to create a panel to get the users address. This panel will contain views to collect the user's address line 1, address line 2, city, state, and zip code. There will also be a save button at the bottom of the panel to save it to a SQLite database. Feel free to skip that part if you already have you're own database set up.

Image: Preview of the Pane


I am going to show you how you can add this panel to your own application. You may reuse the code exactly as is or modify it. If you do use the code the only thing I ask is that you include a link to this blog in your code.


//TODO include links
Post 1: Layout (xml)
Post 2: Activity (java)
Post 3: SQLite Database (java)

Thursday, June 23, 2011

Initial Post

In the coming months I plan to post custom Android controls(views and view groups) that may be useful for other developers.

Labels: