Building the application
The remainder of the tutorial helps you build the Android RSS reader. The explanations are broken into two main sections. The first section discusses the handling of XML data streams, while the following section focuses on the rendering of the RSS data in the Android user interface.
Fetching and parsing XML data with SAX
The core activity in building an RSS reader application is the retrieval and handling of XML data. Fetching XML data is done through an Internet connection and can be accomplished through an HTTP GET operation. A few different classes in Java enable this kind of HTTP transaction. This tutorial demonstrates fetching data with an instance of the URL class. As discussed briefly in the earlier section on application architecture, the SAX parser is employed in this tutorial. SAX parsers require minimal amounts of memory and excel when the data formats are relatively simple in nature or when your data requirements allow selective use of the data structure.
Before you dive into the specifics of using the SAX parser, take a quick look at the RSSFeed and RSSItem classes as they will be referred to extensively throughout the remainder of this tutorial.
The RSSFeed class represents the RSS feed from a high level
perspective. The RSSFeed class includes the elements of interest from the channel portion of the RSS data source as well as a list of RSSItems. The RSSItem represents the individual item in an RSS channel. Look at both of these important classes. RSSFeed.java is found in Listing 4.
Listing 4. RSSFeed.java
package com.msi.androidrss;
import java.util.List;
import java.util.Vector;
import com.msi.androidrss.RSSItem;
public class RSSFeed
{
private String _title = null;
private String _pubdate = null;
private int _itemcount = 0;
private List<RSSItem> _itemlist;
RSSFeed()
{
_itemlist = new Vector(0);
}
int addItem(RSSItem item)
{
_itemlist.add(item);
_itemcount++;
return _itemcount;
}
RSSItem getItem(int location)
{
return _itemlist.get(location);
}
List getAllItems()
{
return _itemlist;
}
int getItemCount()
{
return _itemcount;
}
void setTitle(String title)
{
_title = title;
}
void setPubDate(String pubdate)
{
_pubdate = pubdate;
}
String getTitle()
{
return _title;
}
String getPubDate()
{
return _pubdate;
}
}
|
When the SAX parsing engine is complete with parsing the XML data in your RSS source, an instance of the RSSFeed class has been created and now contains everything required to work with the RSS data in your application. The RSSFeed class contains three important elements, and Set-ers and Get-ers for data manipulation. The data elements of interest are:
- Title (_title): A
Java.lang.Stringto hold the title of the channel for display. - Publication Date (_pubdate): A
java.lang.Stringto hold the publication date for display. Note that in a more sophisticated RSS reader, you can use this publication date information to perform an intelligent update or refresh operation. - List of Items (_itemlist): A parameterized
java.util.Listfor holding a collection of RSSItems.
Let's look at the list of items contained in RSSFeed. Each
element in that list is of type RSSItem. The RSSItem class defines a member of type java.lang.String to hold the value found by the parser. Having each element in a convenient retrieval class makes the application logic and user interface rendering a much more straight-forward task as you will soon see. RSSItem.java is shown in Listing 5.
Listing 5. RSSItem.java
package com.msi.androidrss;
public class RSSItem
{
private String _title = null;
private String _description = null;
private String _link = null;
private String _category = null;
private String _pubdate = null;
RSSItem()
{
}
void setTitle(String title)
{
_title = title;
}
void setDescription(String description)
{
_description = description;
}
void setLink(String link)
{
_link = link;
}
void setCategory(String category)
{
_category = category;
}
void setPubDate(String pubdate)
{
_pubdate = pubdate;
}
String getTitle()
{
return _title;
}
String getDescription()
{
return _description;
}
String getLink()
{
return _link;
}
String getCategory()
{
return _category;
}
String getPubDate()
{
return _pubdate;
}
public String toString()
{
// limit how much text you display
if (_title.length() > 42)
{
return _title.substring(0, 42) + "...";
}
return _title;
}
}
|
An instance of the RSSItem class contains the textual data elements related to an individual item found in the RSS feed. In addition to the java.lang.String fields to hold the data, the class includes a Get-er and Set-er for each field. Lastly, this class overrides the toString() method as this is the method invoked when the Android user interface elements display the list of items. Keep this comment regarding the toString() method in mind as it is required in the discussion of rendering the RSS data in the Android user interface.
As introduced briefly earlier, the SAX approach to XML parsing relies on a callback
structure where the parser scans the data stream to look for tags and invokes methods in
a handler (which you supply) to process the data. The handler extends a Java class
known as the DefaultHandler. There are five methods of
interest in the handler. Each one performs a separate, distinct and important role in the XML parsing process. These methods are:
-
startDocument: Invoked when the document parsing commences. This is an opportunity to initialize required data structures. -
endDocument: Invoked when document parsing concludes. -
startElement: Called when a new tag is encountered by the scanner. Handlers typically use this method to determine location in the document to properly be prepared to store the data when ready. Additionally, any attributes associated with this elements are available for processing, storing and interpreting. -
endElement: Called when a closing tag is encountered by the scanner. Handlers typically use this method to determine location in the document and also to store intermediate data. In the case of the RSS sample application, eachRSSItemis stored into theRSSFeedobject when it encounters the </item> tag. -
characters: Called when data from a tag is available. This is the opportunity to store textual data. For example, when parsing RSS data, the contents of the title, description, link, category, and publication date elements are all stored in this method.
The RSSHandler class is key to the operation of the tutorial application. It is responsible for identifying and storing the relevant information in the XML data stream. Each of its methods is invoked in turn when called upon by the SAX parser.
The RSSHandler implements some very basic state handling to
properly identify and store data as the SAX parser makes its way through the XML data
stream. There are many right ways to manage state in the SAX approach. Though not
necessarily relevant to the topic of RSS feed parsing in this tutorial, if you find that
managing state while parsing a particular XML data source becomes too complex with SAX,
you might consider switching to the DOM parser instead. A DOM parser works on the assumption that every element of the document is important and that your application will desire to explore and extract various elements in the XML data source. SAX works well for an RSS feed because the relationships are so simple. The implementation of RSSHandler.java is shown as Listing 6. Have a quick look at the methods to become familiar with the structure of the handler.
Listing 6. RSSHandler.java
package com.msi.androidrss;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.*;
import android.util.Log;
public class RSSHandler extends DefaultHandler
{
RSSFeed _feed;
RSSItem _item;
String _lastElementName = "";
boolean bFoundChannel = false;
final int RSS_TITLE = 1;
final int RSS_LINK = 2;
final int RSS_DESCRIPTION = 3;
final int RSS_CATEGORY = 4;
final int RSS_PUBDATE = 5;
int depth = 0;
int currentstate = 0;
/*
* Constructor
*/
RSSHandler()
{
}
/*
* getFeed - this returns our feed when all of the parsing is complete
*/
RSSFeed getFeed()
{
return _feed;
}
public void startDocument() throws SAXException
{
// initialize our RSSFeed object - this will hold our parsed contents
_feed = new RSSFeed();
// initialize the RSSItem object - you will use this as a crutch to grab
// the info from the channel
// because the channel and items have very similar entries..
_item = new RSSItem();
}
public void endDocument() throws SAXException
{
}
public void startElement(String namespaceURI, String localName,String qName,
Attributes atts) throws SAXException
{
depth++;
if (localName.equals("channel"))
{
currentstate = 0;
return;
}
if (localName.equals("image"))
{
// record our feed data - you temporarily stored it in the item :)
_feed.setTitle(_item.getTitle());
_feed.setPubDate(_item.getPubDate());
}
if (localName.equals("item"))
{
// create a new item
_item = new RSSItem();
return;
}
if (localName.equals("title"))
{
currentstate = RSS_TITLE;
return;
}
if (localName.equals("description"))
{
currentstate = RSS_DESCRIPTION;
return;
}
if (localName.equals("link"))
{
currentstate = RSS_LINK;
return;
}
if (localName.equals("category"))
{
currentstate = RSS_CATEGORY;
return;
}
if (localName.equals("pubDate"))
{
currentstate = RSS_PUBDATE;
return;
}
// if you don't explicitly handle the element, make sure you don't wind
// up erroneously storing a newline or other bogus data into one of our
// existing elements
currentstate = 0;
}
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException
{
depth--;
if (localName.equals("item"))
{
// add our item to the list!
_feed.addItem(_item);
return;
}
}
public void characters(char ch[], int start, int length)
{
String theString = new String(ch,start,length);
Log.i("RSSReader","characters[" + theString + "]");
switch (currentstate)
{
case RSS_TITLE:
_item.setTitle(theString);
currentstate = 0;
break;
case RSS_LINK:
_item.setLink(theString);
currentstate = 0;
break;
case RSS_DESCRIPTION:
_item.setDescription(theString);
currentstate = 0;
break;
case RSS_CATEGORY:
_item.setCategory(theString);
currentstate = 0;
break;
case RSS_PUBDATE:
_item.setPubDate(theString);
currentstate = 0;
break;
default:
return;
}
}
}
|
Next examine the RSSHandler class a little further. Note
that the class has a single instance of the RSSFeed class.
The purpose of the RSSHandler class is to implement callbacks
from the SAX parser, and in the process of doing so, assemble a representation of the
RSS data for the application to use.
The startElement method assigns which data element was found while the characters method actually performs the assignment to one of the RSSItem members through the appropriate set method. The endElement checks for the end of the item element and when found adds the current RSSItem to the RSSFeed.
The RSSHandler is designed to be self contained for SAX parsing. The methods all react to the parser's events, building up the RSSFeed and then the class makes the fully populated RSSFeed object available through the getFeed method.
Now that you have a feel for what happens when the SAX parser is operating, look at the invocation of the SAX parser. The relevant code is found in the RSSFeed class in the getFeed() method, in Listing 7.
Listing 7.
getFeed() method in RSSFeed.java
private RSSFeed getFeed(String urlToRssFeed)
{
try
{
// setup the url
URL url = new URL(urlToRssFeed);
// create the factory
SAXParserFactory factory = SAXParserFactory.newInstance();
// create a parser
SAXParser parser = factory.newSAXParser();
// create the reader (scanner)
XMLReader xmlreader = parser.getXMLReader();
// instantiate our handler
RSSHandler theRssHandler = new RSSHandler();
// assign our handler
xmlreader.setContentHandler(theRssHandler);
// get our data through the url class
InputSource is = new InputSource(url.openStream());
// perform the synchronous parse
xmlreader.parse(is);
// get the results - should be a fully populated RSSFeed instance,
// or null on error
return theRssHandler.getFeed();
}
catch (Exception ee)
{
// if you have a problem, simply return null
return null;
}
}
|
Following along with the code in Listing 7, you see that you
instantiate both the classes required by the SAX parser and your RSSHandler class. Once you assign our RSSHandler to the XMLReader instance, you can commence with the parse. Remember, the hard work in SAX is defining your handler! But before you can parse data, you have to retrieve it.
The HTTP transaction to fetch the XML data stream takes place through the URL class,
passing its Stream to a new instance of the InputSource
class. The SAX parser/scanner uses the InputSource to
navigate the XML data stream and execute the parsing operation by invoking the methods
in the assigned handler. In this case, those methods are found in the RSSHandler class. Once the parse is complete, the RSSFeed is retrieved from the RSSHandler
which built up an instance of the RSSFeed class during each callback operation along the way.
A parsing operation is attempted within a try/catch block. If the operation is successful, an instance of RSSFeed is returned. If an error occurs, the exception is caught and the function returns null.
With a complete an instance of the RSSFeed class available it is time to render the data to the user.



