Build a Contacts Application With jQuery Mobile & the Android SDK - Part 3

In part 2 of the tutorial series, we demonstrated how to create an account for the newly created contacts. We also described how to edit and delete an existing contact. In Part 3, we will continue the tutorial by explaining how to add a brand new contact. We will also discuss how to use the Android Java API for accessing and manipulating contacts in an Android device.

Adding Contact

On ListPage.html, pressing on the Add button in the header bar (Part 1, Figure 2) invokes a JavaScript function named addContact(). Related sections of the HTML code and addContact() function are listed below. Observe that the function shows the Progress screen immediately and calls the ContactsActivity.addContact() by passing DetailPage.html as the callback page to display.

Related code in ContactsActivity class is shown below.

We had reviewed the showContact() method in 'Existing Contact' section above. Adding a contact is processed very similarly to saving an existing contact with the exception that the contactId parameter is passed as an empty string from the addContact() method. From the discussion in 'Existing Contact' section, the following sequence of method calls takes place for an existing contact (where id is a non-empty string).

When adding a contact, the same sequence will take place, however, id is now an empty string. Let us look at the listing for ContactsActivity.getContact() method again.

The ContactUtility.getContactJSON() returns a special, 'empty', JSON string if the contactId parameter is empty. That special JSON string is shown below.

In Part 2 of this tutorial, we had reviewed the callback JavaScript function setCurrentContact() in DetailPage.html. It will process the above JSON string in such a way that

  • The UI elements represented by variables contactIdVar, firstNameVar, lastNameVar and noteVar are set to empty string.
  • Other UI elements, e.g. phones, are constructed with predefined types and empty input fields. For example, four different types of phone input fields are constructed with empty values for the following types: Home, Work, Mobile and Other.

When saving the contact, the same steps in section 'Saving A Contact' are executed. The ContactUtility.saveOrUpdateContact() method could differentiate between an existing and newly added contact by inspecting its id field. For a newly added contact the id field is empty; as a result, it is saved as a new contact.


Cancel Action On Empty Contact And Existing Contact Screens

Consider the Cancel button in Figure 3 (Part 1), displayed in 'Empty Contact' And 'Existing Contact' screens. When that button is pressed, user is taken back to 'Contact List' screen with no changes on currently edited contact. The HTML listing in DetailPage.html is given below where the button action is defined as showListPage() function.

Below is the listing for showListPage() function. It shows the Progress screen and calls ContactsActivity.showAllContacts() method by passing ListPage.html as the callback page to display.

The ContactsActivity.showAllContacts() method is shown below. It simply loads the callback page without any data processing.


Reading Contacts Via ContactUtility Class

Let us start looking at com.jquerymobile.demo.contact.ContactUtility class that provides utility methods to read and manipulate contacts in contacts database of the Android device.

Getting JSON Formatted List Of Contacts

Recall from Part 1 of this tutorial that the JSON formatted list of contacts is in the following format.

Contacts are grouped by the first initial of the contact's display name, which is value of the key attribute. The display name is constructed by concatenating contact's first and last names. When displayed on UI, value of the key attribute will be used as value of the list divider, which groups contacts whose first initial matching that value. The Jackson JSON Processor creates a JSON document in above format from Java objects inferring field names and types, such as arrays, by inspecting those objects. There are three Java classes that represent the contact list above. Those are

  • ContactDisplay, which represents the leaf level data, namely contactId, displayName and key
  • ContactGroup, which has a collection of ContactDisplay objects for a specific key, e.g. 'A'
  • ContactList, which is the root level class with a collection of ContactGroup objects

Those classes have field names exactly matching the field names in the JSON formatted list above. The Jackson JSON framework provides a variety of options for generating a JSON formatted text from Java objects, including customization of field names. The particular API we use maintains the field names when generating the JSON formatted string from a Java object.

ContactDisplay

As shown in the listing below, ContactDisplay provides contactId, displayName and key attributes for a contact, where key is derived from displayName. What is notable in this class is that it implements the java.lang.Comparable interface to allow sorting of contacts based on displayName attribute. The compareTo() method is required by that interface and it returns one of -1, 0 or 1 depending on whether the first initial of the displayName attribute of the contact is less than, equal to, or greater than that of the contact it is compared with.

ContactGroup

ContactGroup consists of a collection of ContactDisplay objects that all have the same key. ContactGroup's key attribute has the same value as the key attribute the ContactDisplay objects it contains.

ContactList

Listing for ContactList class is given below. This is a very simple data structure that contains a collection of ContactGroup objects.

Generating JSON String

The JSON formatted string for list of contacts is generated in ContactUtility.getAllContactDisplaysJSON() method as shown below.

  • The first step is to get a list of all contacts from the database via getAllContactDisplays() method, details of which we will review later. The contacts are in the form of a list, however they are not sorted yet.
  • The java.util.Collections.sort() method sorts the contact list, using the compareTo() method in ContactDisplay object.
  • In the trivial case of an empty list, a JSON string is returned with no elements in it (notice the constant named EMPTY_CONTACT_LIST). Otherwise, the sorted list is iterated through to establish groups based on keys of the ContactDisplay objects in the list. For each unique key, a ContactGroup object is created and all ContactDisplay objects with that key are stored in that ContactGroup.
  • Each ContactGroup is placed in the root level ContactList is object.
  • Finally, an instance of org.codehaus.jackson.map.ObjectMapper class is created and its write() method is called by passing the root level ContactList object and a StringWriter. The ObjectMapper visits through all fields and objects in the ContactList recursively and generates a JSON text based on field names of those objects. The method call StringWriter.toString() generates the JSON String object.

Getting JSON Formatted String For Contact Details

Recall from Part 2 of this tutorial that the following JSON document represents details for a contact.

We use the Jackson JSON framework to generate that JSON string from the following Java classes.

  • Contact represents the root level data. It contains
    • contactId, firstName, lastName, which are String types
    • note, which is an object type
    • ims, phones, emails, organizations, addresses, which are collections of objects
  • Note represents a note and has rowId and text fields.
  • IM represents a member of ims collection and has rowId, protocol and value fields.
  • Phone represents a member of phones collection and has rowId, type and no fields.
  • Email represents a member of emails collection and has rowId, type and value fields.
  • Organization represents a member of organizations collection and has rowId, type, name and title fields.
  • Address represents a member of addresses collection and has rowId, type, street, city, state, country, zip and poBox fields.

Note that all those classes are simple data holders that provide nothing but getter/setter methods. For brevity, we give a listing of only the root level class Contact below.

Generating The JSON String

The JSON formatted string for contact details is generated in ContactUtility.getContactJSON() method as shown below.

  • If the id of contact is an empty string, getContactJSON() method returns a bare bones JSON string, EMPTY_JSON. (As discussed previously, this is the scenario when a new contact is to be added via the application and we do not have a contact id yet.)
  • If the id is not empty, the getContact() method is called to get an instance of the Contact class from the contacts database in the device. (We will review this method later on.)
  • Then, using the Jackson JSON framework class org.codehaus.jackson.map.ObjectMapper, the Contact object is converted to a JSON formatted string representation and that string is returned to the caller of the method. As discussed in the previous section of contact list generation, that particular technique of creating the JSON string maintains the field names in the Java objects.

Contact Database Table Structure

In Part 4 of this tutorial (next installment), we will look at the application code that deals with the Android contacts API. Before this, we want to give an overview of how contact information is stored in database in an Android device. Various entities related to a contact are stored in database tables that can be accessed via SQL like queries. The Android contacts API provides various classes and methods to represent tables and table columns to store contacts, as well as specific queries that can be run against those tables. Below, we provide information on the Android contacts API as utilized by this tutorial application.

  • android.content.ContentResolver class is used to perform read/write queries in contacts database.
  • android.database.Cursor class is used to iterate through a result set returned from a read query and read individual columns from a particular row.
  • android.content.ContentProviderOperation class is used to define an insert operation in database.
  • android.provider.ContactsContract class and its fields represent contacts related database tables. (See Android documentation on ContactsContract for details.)

For getting information on a contact related entity, one essentially needs three elements.

  • a URI based representation of the table to fetch the data from
  • a 'where' clause for the query; this is constructed using constants in the Android contacts API and parameters created by the application
  • column names to fetch from a row of the result set; these are constants in the Android contacts API

Below, we give a summary of those three elements for the particular read queries used in the tutorial application. For all where clauses, except the one in the query named 'Contacts Associated With An Account', the application provides contactId as the parameter. For the query named 'Contacts Associated With An Account', the application passes the constant com.jquerymobile.demo.contact as the query parameter in the where clause. For the query named 'Contact Id, Contact Display Name', there is no where clause because we fetch all records.

Contacts Database Queries
  • Query: Contact Id, Contact Display Name
    • URI: ContactsContract.Contacts.CONTENT_URI
    • Where clause: none
    • Column names:
      • contactId: ContactsContract.Contacts._ID
      • displayName: ContactsContract.Contacts.DISPLAY_NAME
  • Query: First Name, Last Name
    • URI: ContactsContract.Data.CONTENT_URI
    • Where clause: ContactsContract.RawContactsEntity.CONTACT_ID = ? AND
      ContactsContract.Data.MIMETYPE = ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
    • Column names:
      • firstName: ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME
      • lastName: ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME
  • Query: Address
    • URI: ContactsContract.Data.CONTENT_URI
    • Where clause: ContactsContract.Data.CONTACT_ID = ? AND ContactsContract.Data.MIMETYPE =
      ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE
    • Column names:
      • street: ContactsContract.CommonDataKinds.StructuredPostal.STREET
      • city: ContactsContract.CommonDataKinds.StructuredPostal.CITY
      • state: ContactsContract.CommonDataKinds.StructuredPostal.REGION
      • poBox: ContactsContract.CommonDataKinds.StructuredPostal.POBOX
      • zip: ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE
      • country: ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY
      • rowId: ContactsContract.CommonDataKinds.StructuredPostal._ID
  • Query: Email
    • URI: ContactsContract.CommonDataKinds.Email.CONTENT_URI
    • Where clause: ContactsContract.CommonDataKinds.Email.CONTACT_ID = ?
    • Column names:
      • value: ContactsContract.CommonDataKinds.Email.DATA
      • type: ContactsContract.CommonDataKinds.Email.TYPE
      • rowId: ContactsContract.CommonDataKinds.Email._ID
  • Query: IM
    • URI: ContactsContract.Data.CONTENT_URI
    • Where clause: ContactsContract.Data.CONTACT_ID = ? AND ContactsContract.Data.MIMETYPE =
      ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
    • Column names:
      • value: ContactsContract.CommonDataKinds.Im.DATA
      • protocol: ContactsContract.CommonDataKinds.Im.PROTOCOL
      • rowId: ContactsContract.CommonDataKinds.Im._ID
  • Query: Note
    • URI: ContactsContract.Data.CONTENT_URI
    • Where clause: ContactsContract.Data.CONTACT_ID = ? AND ContactsContract.Data.MIMETYPE =
      ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE
    • Column names:
      • text: ContactsContract.CommonDataKinds.Note.NOTE
      • rowId: ContactsContract.CommonDataKinds.Note._ID
  • Query: Organization
    • URI: ContactsContract.Data.CONTENT_URI
    • Where clause: ContactsContract.Data.CONTACT_ID = ? AND ContactsContract.Data.MIMETYPE =
      ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE
    • Column names:
      • name: ContactsContract.CommonDataKinds.Organization.DATA
      • title: ContactsContract.CommonDataKinds.Organization.TITLE
      • type: ContactsContract.CommonDataKinds.Organization.TYPE
      • rowId: ContactsContract.CommonDataKinds.Organization._ID
  • Query: Phone
    • URI: ContactsContract.CommonDataKinds.Phone.CONTENT_URI
    • Where clause: ContactsContract.CommonDataKinds.Phone.CONTACT_ID = ?
    • Column names:
      • no: ContactsContract.CommonDataKinds.Phone.NUMBER
      • type: ContactsContract.CommonDataKinds.Phone.TYPE
      • rowId: ContactsContract.CommonDataKinds.Phone._ID
  • Query: Contacts Associated With An Account
    • URI: ContactsContract.RawContacts.CONTENT_URI
    • Where clause: ContactsContract.RawContacts.ACCOUNT_TYPE = ?
    • Column name:
      • contactId: ContactsContract.RawContacts.CONTACT_ID

With the above information in mind we can now look at individual methods that query the contacts database.

Getting All Contact Displays

The following method in ContactUtility gets a list of all contacts in the form of a ContactDisplay collection. We had seen that this method was called from ContactUtility.getAllContactDisplaysJSON(). The method uses the 'Contact Id, Contact Display Name' query just discussed above.

  • The ContentResolver object is used to perform a query where a URI representation for the queried table is passed as a parameter.
  • The result is returned as a Cursor instance. Iterating through the cursor, we get the desired fields using constants for column names.
  • At each row of the cursor, a corresponding ContactDisplay object is created from those fields and is placed in a collection.
  • At the end, we close the cursor and return the collection.

Getting First Name, Last Name

The following method in ContactUtility returns a string array where the first element is a contact's first name and the second element is the contact's last name. The method uses the 'First Name, Last Name' query just discussed above.

Construction of the query and processing of the result set are very similar to getAllContactDisplays() method. What is different is the additional parameters passed to the query() method.

  • First parameter to query() is the URI for table.
  • Second parameter to query() is null, requesting all columns. (For additional performance improvement, one may restrict the columns to be returned. For the purposes of the tutorial application, we simply requested all columns from the query although not all columns were used.)
  • Third parameter to query() is the where clause, discussed previously.
  • Fourth parameter to query() is a string array where each element of the array corresponds to the respective parameter, identified by '?', in the where clause.
  • The fifth parameter to query() is null. That parameter could be used to sort the result set. However, we are getting a single result, matching a unique id. Therefore, the sort column is not specified.

Getting Contact Details

The following method in ContactUtility returns a Contact object with details populated from the database where id of the contact is the input parameter. We had seen that this method is called from ContactUtility.getContactJSON().

The method uses various inner query methods. Of those, getFirstNameLastName() has been reviewed just above. The other operations, namely getPhones(), getEmails(), getNote(), getAddresses(), getIms() and getOrganizations() are very similar to getFirstNameLastName() and therefore we omit their reviews for the sake of brevity. Interested reader is referred to the source code of the tutorial. Note that those methods use the previously discussed tables, where clauses and column names, summarized in 'Contacts Database Queries' as follows:

  • getPhones() uses Phone query
  • getEmails() uses Email query
  • getNote() uses Note query
  • getAddresses() uses Address query
  • getIms() uses IM query
  • getOrganizations() uses Organization query

Closing Remarks For Part 3 Of This Tutorial

In Part 3, we continued the tutorial by explaining how to add a brand new contact. We also discussed how to use Android Java API for accessing and manipulating contacts in an Android device. Part 4, the last installment of the tutorial, will explain deleting and saving a contact using Android Java API. In Part 4, we will also describe the development environment for the application, discuss the configuration files for the project, and give individual steps for importing the project into Eclipse IDE.

Tags:

Comments

Related Articles