Creating a Contacts Application

[article] [edit page] [discussion] [history]

From Humanitarian-FOSS Project Development Site

´╗┐Making a contacts application is a bit redundant for a phone like android but it's a good way to get your feet wet on the basics of android databases (SQLite). I have used and simplified the Notepad tutorial that google had created for android. I recommend you to check it out after this.

Contents

Android Database

Android uses the SQLite Databases for saving its files. SQLite is an open source embeddable database engine written in C by D. Richard Hipp. It is entirely self-contained with no external dependencies and is suitable for mobile devices and applications alike. Google exposes the SQLite functionality using the android.database.sqlite package. The recommended way (and arguably more efficient method) to use it within an application is to use Cursor to point to return the findings of the queries and using ContentValues to save data values to the databases.

Contacts Application Objective

We want to demonstrate using the database functionality in the android stack by making a contacts application. Although in real life, such an application might not be used for saving contacts in the phone itself, it can be used to replace the default contacts application if extended properly with the databases exposed using the ContentProvider class. However, the objective here is to use the application to interface with the SQLite database in the phone and save contacts information in them.

Starting out

First of all, we start out with a new eclipse project which can be created from File>New>Project and select Android Project from the dialog. Set the following attributes in the dialog

  • Project name: Contacts
  • Package name: org.hfoss.contacts
  • Activity name: ContactsApp
  • Application name: HFOSS contacts

Making the resources file

We want to add a few strings to the applications, so that we are not hard coding strings and values in our applications. So, open up your res/values/strings.xml file and add these in between the <resources> tags.

   <string name="no_notes">Sorry, no contacts</string>
   <string name="menu_insert">Insert new contact</string>
   <string name="menu_delete">Delete contact</string>
   
   <string name="label_name">Name</string>
   <string name="label_address">Address</string>

Making the interfaces

Applications in android are designed using the xml files at res/layout. So, create a new file from File>New>file and name the file contact_list.xml and put the following content in it: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

   android:layout_width="wrap_content"
   android:layout_height="wrap_content">
 <ListView android:id="@id/android:list"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
 <TextView android:id="@id/android:empty"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/no_notes"/>

</LinearLayout>

Understanding xml file

This xml file is special for ListActivity activity it assigns a ListView if the content is available else it just prints an unavailable entry. You don't need to make a new id anywhere for this to work, android:list and android:empty are built in to android.

We also need an editing activity if we want to allow our users to enter contact information. Follow the instructions as above and create a new file contact_edit.xml with the following content. Remember to download the file (Right Click>Save Link as in firefox) because some browsers try to parse the file and copying that might not give desirable results. Image:Contact edit.xml

Understanding the xml file

The xml file here uses the RelativeLayout for laying out the buttons and EditText views for editing the content.

You need to create another xml file to put the content from the Database to the screen on the list. We want to create a new file contact_row.xml for that with two columns, one for name and other for the phone number.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

     android:layout_width="fill_parent"
       android:layout_height="wrap_content">
       <TextView 
       android:id="@+id/name"
         android:layout_width="130sp"
               android:layout_height="wrap_content"
               android:textSize="20sp"
               android:text="Column1"
   />
       <TextView 
       	android:id="@+id/phonenumber"
         android:layout_width="fill_parent"
               android:layout_height="wrap_content"
               android:textSize="20sp"
               android:text="Column2"
   />

</LinearLayout>

Making DBHelper

We can make a DBHelper class to keep the SQL codes away from the application and avoiding hardcoding everything in the application.

  • Create a new class DBHelper in src>org.hfoss.ContactsApp (In package view in eclipse)

We need the following fields in our table, so we make strings that will help us access the values later from our application. public static final String KEY_NAME = "name"; public static final String KEY_ADDRESS = "address"; public static final String KEY_MOBILE = "mobile"; public static final String KEY_HOME = "home"; public static final String KEY_ROW_ID = "_id"; public static final String PROJECTION[] = { KEY_ROW_ID, KEY_NAME, KEY_ADDRESS, KEY_MOBILE, KEY_HOME }; /* The table and database names */ private static final String TABLE_NAME = "mycontacts"; private static final String DATABASE_NAME = "contactDB";

       /*SQL code for creating the table*/

private static final String TABLE_CREATE= "create table "+ TABLE_NAME + "("+ KEY_ROW_ID +" integer primary key autoincrement,"+ KEY_NAME +" text not null,"+ KEY_ADDRESS + " text not null,"+ KEY_MOBILE + " text,"+ KEY_HOME + " text)";

Initialization

private static final int DATABASE_VERSION = 1;

private SQLiteDatabase db;

public DBHelper(Context ctx){ try {

           db = ctx.openDatabase(DATABASE_NAME, null);
       } catch (FileNotFoundException e) {
           try {
               db =
                   ctx.createDatabase(DATABASE_NAME, DATABASE_VERSION, 0,
                       null);
               db.execSQL(TABLE_CREATE);
           } catch (FileNotFoundException e1) {
               db = null;
           }
       }

} Do a Ctrl+Shift+O to update the import files

Creating and Deleting rows

To save the data to android database that we have created, we need to use the ContentValues to save the key-value pairs which can then be used to insert the row into the table. To delete, we are just checking for the specific rowId and deleting that entry. Updating the row is the same as creating the row but with rowId supplied because we want to update a specific rowId supplied from the ContactEdit activity. public void createRow(String name, String address, String mobile, String home) { ContentValues initialValues = new ContentValues(); initialValues.put(KEY_NAME, name); initialValues.put(KEY_ADDRESS, address); initialValues.put(KEY_MOBILE, mobile); initialValues.put(KEY_HOME, home);

               /*pass the initialValues to the database to insert the row*/

db.insert(TABLE_NAME, null, initialValues); }

       public void deleteRow(long rowId){

db.delete(TABLE_NAME, KEY_ROW_ID+"="+rowId,null); }

       public boolean updateRow (long rowId, String name, String address, String mobile, String home){

ContentValues args = new ContentValues(); args.put(KEY_NAME, name); args.put(KEY_ADDRESS, address); args.put(KEY_MOBILE, mobile); args.put(KEY_HOME, home); return db.update(TABLE_NAME, args, KEY_ROW_ID +"="+ rowId, null)>0; } Do a Ctrl+Shift+O to update the import files

Listing Contacts

We need to list the contacts from the database in the ContactApp activity. Since we want to display the contacts as a list,we want our ContactApp to be an extension of ListActivity and not Activity. So we change Public class ContactApp extends Activity { to Public class ContactApp extends ListActivity { private static final int CONTACT_CREATE = 0; private static final int CONTACT_EDIT = 1;

//select the second one, Android view menu private static final int INSERT_ID = Menu.FIRST; private static final int DELETE_ID = Menu.FIRST + 1;

private DBHelper dbHelper; private Cursor c;


public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.contact_list); dbHelper = new DBHelper(this); fillData(); }

Here, the line 3 gets the layout contact_list.xml and sets it as the view for the Activity when it loads. We want the application to load the data from the database when it loads using the fillData() function. So we create it. private void fillData() { c = dbHelper.fetchAllRows(); startManagingCursor(c); ListAdapter adapter = new SimpleCursorAdapter(this, R.layout.contact_row, c, new String[] { DBHelper.KEY_NAME, DBHelper.KEY_MOBILE }, new int[] { R.id.name, R.id.phonenumber }); setListAdapter(adapter); }

The code here first gets all the Rows in the cursor c and then gets a adapter based on the contact_row layout file we created earlier. It uses the name and mobile number to fill the name and phonenumber columns. It's displayed when the adapter is set as the ListAdapter for the ListActivity. We can see the Adapter design pattern here as ListAdapter adapts(so to say) our cursor for the ListActivity to display. We supply the display Resource Ids int[] { R.id.name, R.id.phonenumber } , the column names that we want to get String[] { DBHelper.KEY_NAME, DBHelper.KEY_MOBILE } , the cursor c and the layout's Resource Id on which to lay them onto (we created it before in res/layout/contact_row). This mapping is automatically done with the Adapter class here.

Getting Contacts

As you might have seen when you pasted the above code, eclipse showed an error for the fetchAllRows() method. So, we make it. Well, there's a much deeper reason for it. Our code just wont make any sense if we don't get any data into it. So, we want to fetch rows to display. The second function fetchAllRows() gets everything from the database. It is good enough for our purposes to get all the columns and rows for displaying in the Activity. The first one looks a bit complicated but it isn't. It just gets the value from the database where rowId is equal to the given row Id.

The concept here to remember is that query function just creates a SQL query for us.

public Cursor fetchRow(long rowId) throws SQLException{ Cursor result = db.query(true, TABLE_NAME, PROJECTION, KEY_ROW_ID + "=" + rowId, null, null, null, null); if ((result.count() == 0) || !result.first()) {

           throw new SQLException("No note matching ID: " + rowId);
       }
       return result;

}

public Cursor fetchAllRows(){ return db.query(TABLE_NAME, PROJECTION, null, null, null, null, null); }

Creating Menus

We want to add options to insert a new contact to the database or delete it. We provide a menu to do so in Android. We add these lines to the public boolean onCreateOptionsMenu(Menu menu) method in the ContactApp.java file menu.add(0, INSERT_ID, R.string.menu_insert); menu.add(0, DELETE_ID, R.string.menu_delete); What this code does is that it adds the items Insert and Delete Contacts to the menu of the Activity. It assigns the menu-group (0 since we want it to be at the top level, the id- Menu.FIRST and Menu.FIRST+1 since we want the delete menu item to follow the insert menu item.

Menu Options

With these menu options, we want to detect the insert menu item and open up an editing screen(ContactEdit.java) to enter the credentials of a new contact and just delete and refresh the list if the delete menu item is selected.

public boolean onMenuItemSelected(int featureId, Item item) { super.onMenuItemSelected(featureId, item); switch (item.getId()) { case INSERT_ID: createContact(); break; case DELETE_ID: dbHelper.deleteRow(c.getLong(c.getColumnIndex(DBHelper.KEY_ROW_ID))); fillData(); break; } return true; }

Creating Contact

We want to open up a new Activity whenever the user wants to create a new contact. We open the ContactEdit Activity whenever the user wants to create a new contact. We assign the CONTACT_CREATE when spawning the sub-Activity for two reasons because for one, anything below 0 is passd to startSubActivity() it creates a new Activity and not the sub-Activity that we wanted to do. So the back button won't work. private void createContact() { Intent i = new Intent(this, ContactEdit.class); startSubActivity(i, CONTACT_CREATE); }

Detecting clicks

We want to open the contact page for the person to view and edit the details everytime the user clicks a list item. What we need to do is, we need to get all the details of the database's current cursor position and send it to the contact editing interface. We use intents to do so. When we spawn an activity, we pass an intent that we want to run the activity with certain characteristics. Why not send some data with it? Here the Bundles come into picture because now we can pass data in the form of key value pairs in the bundle that is passed to an activity. protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id);

Intent i = new Intent(this, ContactEdit.class); i.putExtra(DBHelper.KEY_ROW_ID, c.getLong(c.getColumnIndex(DBHelper.KEY_ROW_ID))); i.putExtra(DBHelper.KEY_NAME, c.getString(c.getColumnIndex(DBHelper.KEY_NAME))); i.putExtra(DBHelper.KEY_ADDRESS, c.getString(c.getColumnIndex(DBHelper.KEY_ADDRESS))); i.putExtra(DBHelper.KEY_MOBILE, c.getString(c.getColumnIndex(DBHelper.KEY_MOBILE))); i.putExtra(DBHelper.KEY_HOME, c.getString(c.getColumnIndex(DBHelper.KEY_HOME))); startSubActivity(i, CONTACT_EDIT); }

Editing Contact

First, we want to get all the views including the TextViews and the submit button. protected void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.contact_edit); nameText = (EditText) findViewById(R.id.textName); addressText = (EditText) findViewById(R.id.textAddress); mobileText = (EditText) findViewById(R.id.textMobile); homeText = (EditText) findViewById(R.id.textHome); Button submitButton = (Button) findViewById(R.id.BtnSave); rowId = null; Now we want to check if we have anything passed to us in the extras Bundle. The code here checks if the intent passed to the Activity has any Extra information in it. It is useful for passing values at instantiation of the Activity as an intent. Bundle extras = getIntent().getExtras();

if (extras != null){ //get all the values for the corresponding keys String name = extras.getString(DBHelper.KEY_NAME); String address = extras.getString(DBHelper.KEY_ADDRESS); String mobile = extras.getString(DBHelper.KEY_MOBILE); String home = extras.getString(DBHelper.KEY_HOME); rowId = extras.getLong(DBHelper.KEY_ROW_ID); We can then set the values for the screen views here, with some check of course. if (name !=null){ nameText.setText(name); } if (address != null){ addressText.setText(address); } if (mobile != null){ mobileText.setText(mobile); } if (home != null){ homeText.setText(home); }

} Now we need to add a listener for the confirm button at the bottom of the view (OK, doesn't matter wherever it is). The Listener is made on the next code block. submitButton.setOnClickListener(confirmButtonListener); }

Saving to the database

We could save the database from here as well, but we want to have a single point for saving database details, so we will pass similar key value pair to the parent database as a bundle. Here, we add the different database keys with their corresponding values and with a RESULT_OK constant(defined in Android SDK), we send the bundle data with setResult. It is passed on to the ContactApp Activity when the application exits. private OnClickListener confirmButtonListener = new OnClickListener(){ public void onClick(View v){ Bundle bundle = new Bundle();

           bundle.putString(DBHelper.KEY_NAME, nameText.getText().toString());
           bundle.putString(DBHelper.KEY_ADDRESS, addressText.getText().toString());
           bundle.putString(DBHelper.KEY_MOBILE, mobileText.getText().toString());
           bundle.putString(DBHelper.KEY_HOME, homeText.getText().toString());
           if (rowId != null) {
               bundle.putLong(DBHelper.KEY_ROW_ID, rowId);
           }
           setResult(RESULT_OK, null, bundle);
           Log.e("what","result set");
           finish();

} };

Let's go back to ContactApp.java where we interpret the results and save to the database. Do you remember sending some constant when spawning a new sub-Activity, this is where it's really useful. If the result code is RESULT_OK, which is returned by Activity if everything is fine, similar to return codes in C. Then we get all the data, like name, address, home, mobile. Now, if it is for a new contact, we run dbHelper.createRow and if it's an update, we just update the database. protected void onActivityResult(int requestCode, int resultCode, String data, Bundle extras) { super.onActivityResult(requestCode, resultCode, data, extras); if (resultCode == RESULT_OK) { String name = extras.getString(DBHelper.KEY_NAME); String address = extras.getString(DBHelper.KEY_ADDRESS); String mobile = extras.getString(DBHelper.KEY_MOBILE); String home = extras.getString(DBHelper.KEY_HOME); switch (requestCode) { case CONTACT_CREATE: dbHelper.createRow(name, address, mobile, home); fillData(); break; case CONTACT_EDIT: Long rowId = extras.getLong(DBHelper.KEY_ROW_ID); if (rowId != null){ dbHelper.updateRow(rowId, name, address, mobile, home); } fillData(); break; } } }

Completed Files

If you want to have a look at the Completed File, it is here Image:ContactApp.zip

References

http://www.ibm.com/developerworks/library/os-sqlite/index.html

Personal tools