Android - Ruby on Rails Project Part 3: Using Android Cursors
In this post, I show how I removed the Project POJOs and marshal the XML data directly into the Android database. Usually I have a layer of objects defining the model API and encapsulating the database, but with Android, Google has pre-written those objects in the form of cursor adapters. Furthermore, there is little chance on a platform like Android that a dramatic change in the back end model will occur, as opposed to a larger less constrained application where a MySQL db is swapped with a Oracle database, then swapped with a distributed data system.
This post follows my former posts:
I changed a bunch of the code from my previous posts which I won’t go through, but as a quick summary:
1. Moved the abstract translator code into it’s own class
2. Using a ListView class to display the projects
3. Created a DB Adapter class to interface with the database
The cool thing about the code changes are that they take advantage of Androids SimpleCursorAdapter to tie the data from the database to the ListView. This removes the need for the Project POJO and simplifies data management. I do have to admit that there’s still a bunch I need to learn about Cursor Adapters, but with a little more practice I think I’ll feel pretty comfortable with them. OK, enough blabbering, let’s get to the code.
The first thing I did was to refactor out the Java Inner Class from the parseDoc method of the SaxParser class. The SaxParser constructor and parseDoc method now look like this.
public class SaxParser
{
private String _path;
private IObjectTranslator _translator;
public SaxParser( String path, IObjectTranslator translator )
{
_path = path;
_translator = translator;
}
public void parseDoc( String doc )
{
try
{
DocHandler handler = new DocHandler( _path, _translator );
Xml.parse(doc, handler);
}
catch ( SAXException e )
{
throw new RuntimeException( e );
}
}
...
} // end SaxParser
I then created a new class ProjectDbTranslator implementing the IObjectTranslater interface to handle the callbacks from the SaxParser specifically to inject the XML data into the Android database instead of creating the Project POJO.
/**
* Takes the callbacks from the Sax Parser and acts on them
* to marshal data from the XML doc to the Database
*
* @author michael
*
*/
class ProjectDbTranslator implements IObjectTranslator
{
private int _id;
private String _name;
private boolean _accepting = false;
public boolean acceptingAttributes()
{
// TODO Auto-generated method stub
return _accepting;
}
/**
* The path that we specified has been achieved,
* initialize the attributes we will
* be gathering, id and name in this example
*/
public void constructObject()
{
_accepting = true;
_id = -1;
_name = "";
}
/**
* Retrieve the attributes we care about
*/
public void setAttribute(String name, String value)
{
if ( name != null )
{
if ( name.trim().equals ( "name" ) )
{
Log.d("attribute", "set name to " + value );
_name = value.trim();
}
else if ( name.trim().equals ( "id" ) )
{
Log.d("attribute", "set id to " + value );
_id = Integer.parseInt ( value.trim() );
}
}
}
/**
* Create a new Project in the database using the name
* defined in the XML.
*/
public void storeObject()
{
_dbHelper.createProject(_name);
_accepting = false;
}
/**
* Bogus method for db injection.
*
* @deprecated
*/
public Object[] getObjectList()
{
return new Object[0];
}
}
A quick summary of ProjectDbTranslator shows the method setAttribute which now assigns the incoming data from the XML doc to the class variables name and id. The name is later used to create a new Project in the database via the storeObject method. The method getObjectList has been deprecated because the list of projects should be retrieved out of the database.
Now that the means of loading an Object Translator into the SaxParser has been re-factored, the call from the main Phobos Activity (which I changed to a ListActivity) now constructs the parser with the “object path” in the XML and an instance of ProjectDbTranslator.
public class Phobos extends ListActivity
{
...
private void retreiveProjects()
{
String url = "http://10.0.1.2:3000/projects?format=xml";
// real server call
// String result = getXmlFromServer(url);
// test call to simply get pre-formed XML
String result = getTestXml();
if ( result != null )
{
Log.i( "phobos", "received " + result );
String path = "/projects/project";
SaxParser parser = new SaxParser( path, new ProjectDbTranslator() );
parser.parseDoc ( result );
}
else
{
Log.i( "phobos", "got a null response" );
}
}
private String getTestXml()
{
return XML;
}
public boolean onOptionsItemSelected(MenuItem item)
{
boolean rtn = false;
switch( item.getItemId() )
{
case 1:
retreiveProjects();
fillData();
return true;
case 2:
_dbHelper.deleteAllProjects();
fillData();
return true;
}
return rtn;
}
/**
* Retreive the projects from the db to display in the project list
*/
private void fillData()
{
Cursor projectsCursor = _dbHelper.fetchAllProjects();
startManagingCursor(projectsCursor);
// Create an array to specify the fields we want to display in the list (only NAME)
String[] from = new String[]{DbAdapter.KEY_NAME};
// and an array of the fields we want to bind those fields to (in this case just text1)
int[] to = new int[]{R.id.text1};
// Now create a simple cursor adapter and set it to display
_adapter = new SimpleCursorAdapter( this,
R.layout.project_row,
projectsCursor,
from,
to );
setListAdapter(_adapter);
}
// test XML document to use
private static final String XML =
"<projects type='array'>" +
"<project>" +
"<created-at type='datetime'>2008-10-01T21:06:51-07:00</created-at>" +
"<id type='integer'>1</id>" +
"<name>Paint House</name>" +
"<updated-at type='datetime'>2008-10-01T21:06:51-07:00</updated-at>" +
"</project>" +
"<project>" +
"<created-at type='datetime'>2008-10-01T21:06:59-07:00</created-at>" +
"<id type='integer'>2</id>" +
"<name>Plan Xmas</name>" +
"<updated-at type='datetime'>2008-10-01T21:06:59-07:00</updated-at>" +
"</project>" +
"</projects>";
...
} // end Phobos class
As you can see, I’ve moved all the XML retrieval code from the retreiveProjects method, which I continue to misspell :), this way I can easily substitute in getTestXml method and simplify the retrieval of the XML string for testing. I did this so I don’t have to worry about having a RoR server running while testing the Android client. The XML string returned from getTestXml represents an exact duplicate of the string I receive back from the RoR server. I also included the method fillData which is call after a menu selection is made by the user to retrieve the data from the server. The fillData method shows the use of a SimpleCursorAdapter to retrieve the project names from the database.
Finally, it’s important to point out that SaxParser was slightly modified to trigger the beginning and ending of “objects” stored in the XML document retrieved from the server. This makes ProjectDbTranslator a little cleaner.
public class SaxParser
{
...
class DocHandler extends DefaultHandler
{
...
public void startElement ( String uri,
String localName,
String name,
Attributes attributes )
throws SAXException
{
_currentPath.append( "/" + localName );
if ( _currentPath.toString ( ).equals ( _objectPath ) )
{
if ( !_firstObject )
{
// before constructing a new object, store the last one
_translator.storeObject();
}
// we've arrived at a path defining one of our objects
_translator.constructObject ( );
_firstObject = false;
}
else if ( _translator.acceptingAttributes ( ) )
{
// we've instantiated an object are looking for
// possible attributes to fill it with
Log.i("handler", "accept attribute " + localName );
_currentAttribute = localName;
}
}
public void endDocument ( ) throws SAXException
{
// we're done with the document, trigger the last
// object to store itself
_translator.storeObject ( );
}
} // end DocHandler
...
} // end SaxParser
There is certainly much more code involved in my latest version of Phobos, and I’d be more then happy to send you a zipped up set of the Android files if you want to drop me an email (I may set up a Googe Project if there is enough interest). As with my previous posts I don’t feel like there is anything revolutionary here, I just had an epiphany as I was writing my last blog post that the Project POJO was not much use and in the mobile world, I’m reading that less objects are better, as stated in Googles Android documentation, “Avoid Creating Objects“.
Not sure what I’m going to do next, either creating a security model (big additions) or maybe POSTing data to the server via a REST call. Don’t forget to drop me a line at michael@mgmblog.com if you want the current state of the code.

June 1st, 2009 at 6:51 pm
Great post Michael, you’ve covered a lot of stuff in your three part series! Very impressed. You’ve inspired me to get off my ace and get back into my project!