Android code After a rather long introduction and stage setting for this chapter, it’s time to look at

12.3 Android code After a rather long introduction and stage setting for this chapter, it’s time to look at

the source code for the Field Service Application. The approach is to largely follow the application flow, step-by-step. Let’s start with the splash screen.

12.3.1 Splash Activity We are all very familiar with a splash screen for a software application. It acts like a cur-

tain while important things are taking place behind the scenes. Ordinarily splash screens are visible until the application is ready—this could be a very brief amount of time or much longer in the case where quite a bit of housekeeping is necessary. As a rule, a mobile application should focus on economy and strive to have as little resource consumption as possible. The splash screen in this sample application is meant to dem- onstrate how such a feature may be constructed—we don’t actually need one for house- keeping purposes. But that’s okay; you can learn in the process. Two code snippets are of interest to us, the implementation of the Activity as well as the layout file that defines what the UI looks like. First, examine the layout file in listing 12.2.

Listing 12.2 splash.xml defines the layout of the application’s splash screen.

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

android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" >

B Full screen

<ImageView

ImageView

android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="fitCenter"

C Image

android:src="@drawable/android"

reference

/> </LinearLayout>

The splash.xml layout contains a single ImageView B , set to fill the entire screen. The source image for this view is defined as the drawable resource C , named android.

Note that this is simply the name of the file (minus the file extension) in the drawable folder, as shown earlier.

Now we must use this layout in an Activity. Aside from the referencing of an image resource from the layout, this is really not that interesting. Figure 12.5 shows the splash screen running on the Android Emulator.

Android code

Figure 12.5 Splash screen

Of interest to us is the code that creates the splash page functionality. This code is shown in listing 12.3.

Listing 12.3 Splash.java implements splash screen functionality.

package com.msi.manning.UnlockingAndroid; // multiple imports omitted for brevity, see full source code public class Splash extends Activity {

@Override public void onCreate(Bundle icicle) {

super.onCreate(icicle);

B Set up main View

setContentView(R.layout.splash); Handler x = new Handler();

C Define and set

x.postDelayed(new splashhandler(), 2000);

up Handler

} class splashhandler implements Runnable {

D Implement Handler

C HAPTER 12 Putting it all together–the Field Service Application

public void run() {

E Start

startActivity(

application’s

new Intent(getApplication(),FieldService.class));

main Activity

Splash.this.finish(); }

F Kill the splash screen

Like most Activity classes in Android, we want to associate the splash layout with this

Activity ’s View B . A Handler is set up C , which is used to close down the splash

screen after an elapsed period of time. Note that the arguments to the postDelayed method are an instance of a class that implements the Runnable interface and the desired elapsed time in milliseconds. In this snippet of code, the screen will be shown for 2000 milliseconds, or 2 seconds. After the indicated amount of time has elapsed,

the class splashhandler D is invoked. The FieldService Activity is instantiated with a call to startActivity E . Note that an Intent is not used here—we explicitly

specify which class is going to satisfy our request. Once we have started the next

Activity , it is time to get rid of our splash screen Activity, F .

The splash screen is happily entertaining our mobile worker each time he starts the application. Let’s move on to the main screen of the application.

12.3.2 FieldService Activity, part 1 The goal of the FieldService Activity is to put the functions the mobile worker

requires directly in front of him and make sure they are easy to access. A good mobile application is often one that can be used with one hand, such as the five-way navigation buttons, or in some cases a thumb tapping on a button. In addition, if there is helpful information to display, you should not hide it. It is helpful for our mobile worker to know that he is configured to obtain jobs from a particular server. Figure 12.6 demon- strates the Field Service Application convey- ing a very simple, yet easy-to-use home screen.

Before reviewing the code in FieldSer- vice.java, we need to take a break to discuss how the user and server settings are man- aged. This is important because these set- tings are used throughout the application, and as shown in the fieldservice.xml layout file, we need to access those values to display to our mobile worker on the home screen.

Figure 12.6 The home screen. Less is more. PREFS CLASS As you learned in chapter 5, there are a number of means for managing data. Because we need to persist this data across multiple invocations of our application, the data must

be stored in a nonvolatile fashion. This application employs private SharedPreferences to accomplish this. Why? Despite the fact that we are largely ignoring security for this sample application, using private SharedPreferences means that other applications

Android code

cannot casually access this potentially important data. For example, we presently use only an identifier (let’s call it an email address for simplicity) and a server URL in this application. However, we might also include a password or a PIN in a production-ready application, so keeping this data private is a good practice.

The Prefs class can be described as a helper or wrapper class. This class wraps the SharedPreferences code and exposes simple getter and setter methods, specific to this application. This implementation knows something about what we are trying to accomplish, so it adds value with some default values as well. Let’s look at listing 12.4 to see how our Prefs class is implemented.

Listing 12.4 Prefs class provides storage and retrieval for small and useful information.

package com.msi.manning.UnlockingAndroid; // multiple imports omitted for brevity, see full source code public class Prefs {

B SharedPreferences object

private SharedPreferences _prefs = null; private Editor _editor = null;

C Implement Handler

private String _useremailaddress = "Unknown"; private String _serverurl =

D Default

"http://android12.msi-wireless.com/getjoblist.php";

values

public Prefs(Context context) { _prefs =

E Initialize SharedPreferences

context.getSharedPreferences("PREFS_PRIVATE",Context.MODE_PRIVATE);

_editor = _prefs.edit(); } public String getValue(String key,String defaultvalue){

if (_prefs == null) return "Unknown"; return _prefs.getString(key,defaultvalue);

f Generic

set/get

public void setValue(String key,String value) {

methods

if (_editor == null) return; _editor.putString(key,value); } public String getEmail(){

g Extract email value

if (_prefs == null) return "Unknown";

_useremailaddress = _prefs.getString("emailaddress","Unknown"); return _useremailaddress;

} public void setEmail(String newemail) {

h Set email value

if (_editor == null) return; _editor.putString("emailaddress",newemail);

} … (abbreviated for brevity) public void save() {

i Save preferences

if (_editor == null) return; _editor.commit(); } }

C HAPTER 12 Putting it all together–the Field Service Application

To persist the application’s settings data, we employ a SharedPreferences object B .

To manipulate data within the SharedPreferences object, here named simply _prefs,

we use an instance of the Editor class C . This snippet employs some default settings values D , which are appropriate for our application. The Prefs() constructor E does the necessary housekeeping so we can establish our private SharedPreferences object, including using a passed-in Context instance. The Context class is necessary because the SharedPreferences mechanism relies on a Context for segregating data.

This snippet shows a pair of set and get methods that are generic in nature F . The getEmail G and setEmail methods H are responsible for manipulating the email setting value. The save() method I invokes a commit() on the Editor, which per-

sists the data to the SharedPreferences store. Now that you have some feel for how this important preference data is stored, let’s return to examine the code of FieldService.java.

12.3.3 FieldService Activity, part 2 Recall that the FieldService.java file implements the FieldService class, which is

essentially the home screen of our application. This code does the primary dispatch- ing for the application. Many of the programming techniques in this file have been shown earlier in the book; however, please take note of the use of startActivityFor- Result and the onActivityResult methods as you read through the code, as shown in listing 12.5.

Listing 12.5 FieldService.java implements FieldService Activity

package com.msi.manning.UnlockingAndroid; // multiple imports trimmed for brevity, see full source code public class FieldService extends Activity {

final int ACTIVITY_REFRESHJOBS = 1; final int ACTIVITY_LISTJOBS = 2;

Useful constants

final int ACTIVITY_SETTINGS = 3; Prefs myprefs = null;

B Prefs instance

@Override

C Instantiate Prefs

public void onCreate(Bundle icicle) {

Set up UI

instance

super.onCreate(icicle); setContentView(R.layout.fieldservice);

Initiate

myprefs = new Prefs(this.getApplicationContext());

UI field

RefreshUserInfo();

contents

final Button refreshjobsbutton = (Button) findViewById(R.id.getjobs); refreshjobsbutton.setOnClickListener(new Button.OnClickListener() {

public void onClick(View v) {

Connect

try {

button to UI

startActivityForResult(new

Intent(v.getContext(),RefreshJobs.class),ACTIVITY_REFRESHJOBS); } catch (Exception e) { } }

Android code

// see full source comments } @Override protected void onActivityResult(int requestCode, int resultCode, Intent

data) { switch (requestCode) { case ACTIVITY_REFRESHJOBS: break;

E case ACTIVITY_LISTJOBS: onActivityResult

processing

break; case ACTIVITY_SETTINGS: RefreshUserInfo(); break;

} } private void RefreshUserInfo() {

F RefreshUserInfo

try { final TextView emaillabel = (TextView) findViewById(R.id.emailaddresslabel); emaillabel.setText("User: " + myprefs.getEmail() + "\nServer: " + myprefs.getServer() + "\n"); } catch (Exception e) { } } }

This code implements a simple UI that displays three distinct buttons. As each is selected, a particular Activity is started in a synchronous, call/return fashion.

The Activity is started with a call to startActivityForResult D . When the called

Activity is complete, the results are returned to the FieldService Activity via the

onActivityResult method E . An instance of the Prefs class B , C is used to obtain

values for displaying in the UI. Updating the UI is accomplished in the method

RefreshUserInfo F .

Because the settings are so important to this application, the next section covers the management of the user and server values.

12.3.4 Settings When the user clicks the Settings button from the main application screen, an Activ-

ity is started that allows the user to configure his user ID (email address) and the server URL. The screen layout is very basic (listing 12.6). It is shown graphically in fig- ure 12.7.

Listing 12.6 showsettings.xml contains UI elements for the settings screen

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

android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" >

TextView for

<TextView

display of labels

C HAPTER 12 Putting it all together–the Field Service Application

android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Email Address" /> <EditText android:id="@+id/emailaddress" android:layout_width="fill_parent" android:layout_height="wrap_content" android:autoText="true" />

TextView for

<TextView

display of labels

EditView for

android:layout_width="fill_parent"

entry of data

android:layout_height="wrap_content" android:text="Server URL" /> <EditText android:id="@+id/serverurl" android:layout_width="fill_parent" android:layout_height="wrap_content" android:autoText="true" /> <Button android:id="@+id/settingssave" android:text="Save Settings"

Button to initiate

android:layout_height="wrap_content"

saving data

android:layout_width="wrap_content" android:enabled="true" />

</LinearLayout> The source code behind the settings screen is

also very basic. Note the use of the Populate- Screen() method, which makes sure the EditView controls are populated with the cur- rent values stored in the SharedPreferences. Note also the use of the Prefs helper class to retrieve and save the values, as shown in list- ing 12.7

Figure 12.7 Settings screen in use

Listing 12.7 ShowSettings.java implements the code behind the settings screen

package com.msi.manning.UnlockingAndroid; // multiple imports trimmed for brevity, see full source code public class ShowSettings extends Activity {

Prefs myprefs = null; @Override public void onCreate(Bundle icicle) {

Initialize Prefs B

super.onCreate(icicle);

instance

setContentView(R.layout.showsettings);

C Populate UI

myprefs = new Prefs(this.getApplicationContext());

elements

PopulateScreen(); final Button savebutton = (Button) findViewById(R.id.settingssave); savebutton.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) {

Android code

try { final EditText email= (EditText)findViewById(R.id.emailaddress); if (email.getText().length() == 0) {

// display dialog, see full source code return;

D Connect

EditText to UI

final EditText serverurl = (EditText)findViewById(R.id.serverurl); if (serverurl.getText().length() == 0) { // display dialog, see full source code return;

} myprefs.setEmail(email.getText().toString());

E myprefs.setServer(serverurl.getText().toString()); Store and

save settings

myprefs.save(); finish();

Finish this

} catch (Exception e) {

F Activity

G PopulateScreen

private void PopulateScreen() {

method sets up UI

try { final EditText emailfield = (EditText) findViewById(R.id.emailaddress); final EditText serverurlfield = (EditText)findViewById(R.id.serverurl); emailfield.setText(myprefs.getEmail()); serverurlfield.setText(myprefs.getServer()); } catch Exception e) { }

This Activity commences by initializing the SharedPreferences instance B , which retrieves the settings values and subsequently populates the UI elements C by calling the application-defined PopulateScreen method G . When the Save Settings button is

clicked, the onClick method is invoked, wherein the data is extracted from the UI ele-

ments D and put back into the Prefs instance E . A call to the finish method F

ends this Activity. Once the settings are in order, it’s time to focus on the core of the application, managing jobs for our mobile worker. In order to get the most out of looking at the higher-level functionality of downloading (refreshing) and managing jobs, we need to look at the core data structures in use in this application.

12.3.5 Data structures Data structures represent a key element of any software project and, in particular, one

consisting of multiple tiers, such as this Field Service Application. Job data is exchanged between an Android application and the server, so the elements of the job are central to our application. In Java, we implement these data structures as classes, which include helpful methods in addition to the data elements. XML data shows up in many locations in this application, so we will start there.

C HAPTER 12 Putting it all together–the Field Service Application

JOB XML DESCRIPTION

The same XML format is used as persistent storage by the Android application and for the transmission of job data from the server to Android. There is nothing particularly fancy in the XML document structure, just a collection of jobs.

Perhaps the most straightforward means of describing an XML document is through a Document Type Definition, or DTD. The DTD representing the XML used in this application is shown in listing 12.8.

Listing 12.8 DTD for joblist.xml

<?xml version="1.0" encoding="UTF-8"?>

B joblist grouping

<!ELEMENT joblist ((job+))> <!ELEMENT job ((id, status, customer, address, city, state, zip, product,

producturl, comments))> <!ELEMENT zip (#PCDATA)>

C job element fields

<!ELEMENT status (#PCDATA)> <!ELEMENT state (#PCDATA)> <!ELEMENT producturl (#PCDATA)> <!ELEMENT product (#PCDATA)> <!ELEMENT id (#PCDATA)> <!ELEMENT customer (#PCDATA)> <!ELEMENT comments (#PCDATA)> <!ELEMENT city (#PCDATA)> <!ELEMENT address (#PCDATA)>

The joblist B is the top level of the xml file, containing one or more job elements C .

Listing 12.9 shows a sample XML document containing a joblist with a single job entry.

Listing 12.9 XML document containing data for the Field Service Application

<?xml version="1.0" encoding="UTF-8" ?> <joblist> <job> <id>22</id> <status>OPEN</status> <customer>Big Tristan's Imports</customer> <address>2200 East Cedar Ave</address> <city>Flagstaff</city> <state>AZ</state> <zip>86004</zip> <product>UnwiredTools UTCIS-PT</product> <producturl>http://unwiredtools.com</producturl> <comments>Requires tuning - too rich in the mid range RPM. Download software from website before visiting.</comments> </job> </joblist>

Now that you have a feel for what the job data looks like, you need to see how the data is handled in our Java classes. JOBENTRY The individual job is used throughout the application and is therefore essential to understand. In our application, we define the JobEntry class to manage the individual

Android code

job, shown in listing 12.10. Note that many of the lines are omitted from this listing for brevity; please see the available source code for the complete code listing.

Listing 12.10 JobEntry.java

package com.msi.manning.UnlockingAndroid; import android.os.Bundle;

B Bundle class import

public class JobEntry { private String _jobid="";

Each member

private String _status = "";

C is a String

// members omitted for brevity private String _producturl = ""; private String _comments = "";

JobEntry() { } // get/set methods omitted for brevity

D toString method

public String toString() { return this._jobid + ": " + this._customer + ": " + this._product; } public String toXMLString() {

toXMLString

StringBuilder sb = new StringBuilder("");

E method

sb.append("<job>"); sb.append("<id>" + this._jobid + "</id>"); sb.append("<status>" + this._status + "</status>"); sb.append("<customer>" + this._customer + "</customer>"); sb.append("<address>" + this._address + "</address>"); sb.append("<city>" + this._city + "</city>"); sb.append("<state>" + this._state + "</state>"); sb.append("<zip>" + this._zip + "</zip>"); sb.append("<product>" + this._product + "</product>"); sb.append("<producturl>" + this._producturl + "</producturl>"); sb.append("<comments>" + this._comments + "</comments>"); sb.append("</job>"); return sb.toString() + "\n";

F toBundle

public Bundle toBundle() {

method

Bundle b = new Bundle(); b.putString("jobid", this._jobid); b.putString("status", this._status); // assignments omitted for brevity b.putString("producturl", this._producturl); b.putString("comments", this._comments); return b;

G fromBundle

public static JobEntry fromBundle(Bundle b) {

method

JobEntry je = new JobEntry(); je.set_jobid(b.getString("jobid")); je.set_status(b.getString("status")); // assignments omitted for brevity

je.set_producturl(b.getString("producturl")); je.set_comments(b.getString("comments")); return je;

C HAPTER 12 Putting it all together–the Field Service Application

This application relies heavily on the Bundle class B for moving data from one

Activity to another. This will be explained in more detail later in this chapter. A

String member C exists for each element in the job such as jobid, customer, and so on. The toString() method D is rather important as it is used when displaying

jobs in the ManageJobs Activity, discussed later in the chapter. The toXML-

String() method E generates an XML representation of this JobEntry, complying

with the job element defined in the previously presented DTD. The toBundle()

method F takes the data members of the JobEntry class and packages them into a

Bundle . This Bundle is then able to be passed between activities, carrying with it the

required data elements. The fromBundle() static method G returns a JobEntry

when provided with a Bundle. The toBundle() and fromBundle() work together to assist in the passing of JobEntry objects (at least the data portion thereof) between activities. Note that this is one of many ways in which to move data throughout an application. Another method would be to have a globally accessible class instance to store data, for example.

Now that you understand the JobEntry class, we need to look at the JobList class, which is a class used to manage a collection of JobEntry objects. JOBLIST When interacting with the server or presenting the available jobs to manage on the Android device, the Field Service Application works with an instance of the JobList class. This class, like the JobEntry class, has both data members and helpful methods. The JobList class contains a typed List data member, which is implemented using a Vector . This is the only data member of this class, as shown in listing 12.11. The meth- ods of interest are described in the listing.

Listing 12.11 JobList.java code listing

package com.msi.manning.UnlockingAndroid; B List class imported

C InputSource

import java.util.List;

for Vector

imported, used

import org.xml.sax.InputSource;

by XML parser

import android.util.Log; // additional imports omitted for brevity, see source code

Familiar logging mechanism

public class JobList { private Context _context = null; private List<JobEntry> _joblist; JobList(Context context){

D Constructor

_context = context; _joblist = new Vector<JobEntry>(0);

} int addJob(JobEntry job){

_joblist.add(job); return _joblist.size();

E addJob/getJob

methods

JobEntry getJob(int location) { return _joblist.get(location); }

Android code

List<JobEntry> getAllJobs() {

getAllJobs

return _joblist;

F method

} int getJobCount() {

return _joblist.size(); }

G replace

void replace(JobEntry newjob){

method

try { JobList newlist = new JobList(); for (int i=0;i<getJobCount();i++) {

JobEntry je = getJob(i); if (je.get_jobid().equals(newjob.get_jobid())) {

newlist.addJob(newjob); } else { newlist.addJob(je); } } this._joblist = newlist._joblist; persist();

} catch (Exception e) { }

H persist method writes

void persist() {

data to storage

try { FileOutputStream fos = _context.openFileOutput("chapter12.xml", Context.MODE_PRIVATE); fos.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n".getBytes()); fos.write("<joblist>\n".getBytes()); for (int i=0;i<getJobCount();i++) {

JobEntry je = getJob(i); fos.write(je.toXMLString().getBytes());

} fos.write("</joblist>\n".getBytes()); fos.flush(); fos.close();

} catch (Exception e) { Log.d("CH12",e.getMessage()); } }

I parse

static JobList parse(Context context) {

method

try { FileInputStream fis = context.openFileInput("chapter12.xml"); if (fis == null) {

return null; } InputSource is = new InputSource(fis); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); XMLReader xmlreader = parser.getXMLReader(); JobListHandler jlHandler =

new JobListHandler(null /* no progress updates when reading file */); xmlreader.setContentHandler(jlHandler); xmlreader.parse(is); fis.close(); return jlHandler.getList();

C HAPTER 12 Putting it all together–the Field Service Application

} catch (Exception e) { return null; } } }

The list of jobs is implemented as a Vector, which is a type of List B . The XML struc-

ture containing job information is parsed with the SAX parser, so we need to be sure to

import those required packages C . JobEntry objects are stored in the typed List object named _joblist D . Helper methods for managing the list are included as addJob and getJob E . The getAllJobs() method F returns the list of JobEntry

items. Note that generally speaking the application uses the getJob() method for individual JobEntry management; however, the getAllJobs() method is particularly useful when we get to displaying the full list of jobs in the ManageJobs Activity, dis- cussed later in this chapter.

The replace() method G is used when we have closed a job and need to update

our local store of jobs. Note that after it has updated the local list of JobEntry items, it

calls the persist() H method, which is responsible for writing an XML representa-

tion of the entire list of JobEntry items to storage. This method invokes the toXML- String() method on each JobEntry in the list. The openFileOutput method creates

a file within the application’s private file area. This is essentially a helper method to ensure we get a file path to which we have full read/write privileges.

Finally, the parse method I obtains an instance of a FileInputStream to gain access to the file and then creates an instance of an InputStream C , which is required

by the SAX XML parser. In particular, take note of the JobListHandler. SAX is a call- back parser, meaning that it invokes a user-supplied method to process events in the parsing process. It is up to the JobListHandler (in our example) to process the data as appropriate.

We have one more class to go before we can jump back to the higher-level function- ality of our application. The next section takes a quick tour of the JobListHandler, which is responsible for putting together a JobList from an XML data source.

JOBLISTHANDLER

As presented already, our application uses an XML data storage structure. This XML data can come from either the server or from a local file on the filesystem. In either case, the application must parse this data and transform it into a useful form. This is accomplished through the use of the SAX XML parsing engine and the JobList- Handler , which is shown in listing 12.12. The JobListHandler is used by the SAX parser for our XML data, regardless of the data’s source. Where the data comes from dictates how the SAX parser is set up and invoked in this application. The JobListHandler behaves slightly differently depending on whether or not the class’s constructor includes a Handler argument. If the Handler is provided, the JobListHandler will pass messages back for use in a ProgressDialog. If the Handler argument is null, this status message passing is bypassed. When parsing data from the server, the ProgressDialog is employed; the parsing of a local file is done quickly and without user feedback. The

Android code

rationale for this is simple—the network connection may be slow and we need to show progress information to the user. An argument could be made for always showing the progress of the parse operation, but this approach gives us opportunity to demonstrate more conditionally operating code.

Listing 12.12 JobListHandler.java

package com.msi.manning.UnlockingAndroid; // multiple imports omitted for brevity, see full source code public class JobListHandler extends DefaultHandler {

Handler phandler = null; JobList _list; JobEntry _job; String _lastElementName = ""; StringBuilder sb = null; Context _context;

B JobListHandler

JobListHandler(Context c,Handler progresshandler) {

constructor

_context = c; if (progresshandler != null) {

C Check for

phandler = progresshandler;

progress handler

Message msg = new Message(); msg.what = 0; msg.obj = (Object)("Processing List"); phandler.sendMessage(msg);

D getList

public JobList getList() {

method

Message msg = new Message(); msg.what = 0; msg.obj = (Object)("Fetching List"); if (phandler != null) phandler.sendMessage(msg); return _list;

E startDocument

public void startDocument() throws SAXException {

method

Message msg = new Message(); msg.what = 0; msg.obj = (Object)("Starting Document"); if (phandler != null) phandler.sendMessage(msg); _list = new JobList(_context); _job = new JobEntry();

F endDocument

public void endDocument() throws SAXException {

method

Message msg = new Message(); msg.what = 0; msg.obj = (Object)("End of Document"); if (phandler != null) phandler.sendMessage(msg);

} public void startElement

➥ (String namespaceURI, String localName,String qName, ➥ Attributes atts) throws SAXException {

try { sb = new StringBuilder("");

C HAPTER 12 Putting it all together–the Field Service Application

if (localName.equals("job")) { Message msg = new Message(); msg.what = 0; msg.obj = (Object)(localName); if (phandler != null) phandler.sendMessage(msg); _job = new JobEntry();

} } catch (Exception ee) { }

} public void endElement

➥ (String namespaceURI, String localName, String qName) ➥ throws SAXException {

if (localName.equals("job")) {

Check for end

// add our job to the list!

G of job element

_list.addJob(_job); Message msg = new Message(); msg.what = 0; msg.obj = (Object)("Storing Job # " + _job.get_jobid()); if (phandler != null) phandler.sendMessage(msg); return;

} // portions of the code omitted for brevity } public void characters(char ch[], int start, int length) {

String theString = new String(ch,start,length); Log.d("CH12","characters[" + theString + "]"); sb.append(theString);

Build up String

H incrementally

The JobListHandler constructor B takes a single argument of Handler. This value

may be null. If null, Message passing is omitted from operation, as we will show. When reading from a local storage file, this Handler argument is null. When reading data from the server over the internet, with a potentially slow connection, the Message- passing code is utilized to provide feedback for the user in the form of a Progress- Dialog . The ProgressDialog code is shown later in this chapter in the discussion of

the RefreshJobs Activity. A local copy of the Handler C is set up when using the

ProgressDialog , as described in B . The getList() D method is invoked when parsing is complete. The role of

getList is to return a copy of the JobList that was constructed during the parse pro-

cess. When the startDocument() callback method E is invoked by the SAX parser, the initial class instances are established. The endDocument() method F is invoked by

the SAX parser when all of the document has been consumed. This is an opportunity for the Handler to perform additional cleanup as necessary. In our example, a mes- sage is posted to the user by sending a Message.

For each element in the XML file, the SAX parser follows the same pattern: start- Element is invoked, characters() is invoked (one or more times), and endElement is invoked. In the startElement method, we initialize our StringBuilder and evaluate the element name. If the name is “job,” we initialize our class-level JobEntry instance.

Digging deeper into the code

In the endElement() method, the element name is evaluated. If the element name

is “job” G , the JobListHandler adds this JobEntry to the JobList data member, _joblist with a call to addJob(). Also in the endElement() method, the data mem- bers of the JobEntry instance (_job) are updated. Please see the full source code for more details.

The characters() method is invoked by the SAX parser whenever data is available for storage. The JobListHandler simply appends this string data to a StringBuilder

instance H each time it is invoked. It is possible that the characters method is

invoked more than once for a particular element’s data. That is the rationale behind using a StringBuilder instead of a single String variable; StringBuilder is a more efficient class for constructing strings from multiple substrings.

After this lengthy but important look into the data structures and the accompanying explanations, it is time to return to the higher-level functionality of the application.