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.