Create BlackBerry applications with open source tools, Part 2: Building an RSS reader

In the same way that open source has revolutionized the software development marketplace, the proliferation of alternative news sources has shaken up the traditional news-source monopolies. Today, you can get free news from various Internet sources and from the Internet outlets of the mainstream media. Trekking from one Web site to another to read news stories is possible, but tedious. And what about updates to news stories of interest? Wouldn't it be better if the news of interest is aggregated for your convenience? You can do it with an RSS reader for BlackBerry. Here in Part 2 of this "Create BlackBerry applications with open source tools" series, explore the RSS data-distribution format by creating a BlackBerry RSS reader suitable for taking news wherever you and your BlackBerry go.

Share:

Frank Ableson, Author

After his college basketball career came to an end without a multiyear contract to play for the L.A. Lakers, Frank Ableson shifted his focus to computer software design. He enjoys solving complex problems, particularly in the areas of communications and hardware interfacing. When not working, he can be found spending time with his wife Nikki and their children. You can reach Frank at frank@cfgsolutions.com.



17 February 2009

Also available in Spanish

Before you start

This series explores open source and Java™ technology for BlackBerry application development in the context of a mobile data-collection application. Part 1 provides an introduction to BlackBerry development with a quick introduction to the platform, a tour of the BlackBerry development tools, and construction of a complete data-collection application.

This tutorial focuses on BlackBerry application development using an example of a mobile content-management application. The example demonstrates a basic RSS reader leveraging the freely available BlackBerry development tools. Mobile development experience is helpful, but not required. Java programming skills are required for BlackBerry applications, but are not an explicit requirement for this tutorial. RSS feeds are the source of the data content used by the sample application. Familiarity with RSS is helpful, but not required if you just want an understanding of a mobile content-management application architecture.

About this tutorial

Why be concerned about writing an RSS reader for BlackBerry? The BlackBerry, and all cell phones, are an integral part of life today. Our mobile devices have increasingly functional user interfaces (UIs) and increased technical capabilities in terms of data rates, storage capacity, and processor speed. But if there is no content to enjoy on these mobile powerhouses, the devices are of marginal use beyond the phone. We need content on our devices. Remember "The pen is mightier than the sword?" The written word matters. And today, the written word is digital — and mobile. The free flow of information and the freedom to subscribe to news of interest is the foundation of a free and open society. What better application of open source technology than an RSS reader to subscribe to free sources of information?

This tutorial uses the Java programming language to build a mobile news reader for the BlackBerry platform. There are commercial RSS readers for mobile devices, but it is not the objective of this tutorial to compete with the commercial applications. This tutorial shows how to build an open source application to help you build useful applications centered on content and its distribution. Learn about managing RSS feeds representing news items from a variety of sources. You can use this same paradigm in other applications, such as mobile fleet-management data, worker-dispatch data, or even implementing a mobile search engine.

Though it's a popular platform, third-party applications are still needed for the BlackBerry. There is no better way to bring those applications to fruition than to enable the open source community. Follow along as this tutorial lays the groundwork for an open source RSS reader application, which you can readily expand and retool for other useful purposes.

This tutorial provides a brief introduction to RSS, then dives into the requirements for a mobile RSS reader for BlackBerry. You can download the complete source code for the BlackBerry application.

System requirements

This tutorial demonstrates how to use BlackBerry development tools to construct an open source RSS reader for BlackBerry. You will need the BlackBerry Java Development Environment (JDE) or equivalent to construct the application. This tutorial uses V4.0.2 of the JDE. The tutorial includes a few sample RSS feed links, but you can substitute your own links as desired.

Sample code highlights

In this tutorial, a mobile RSS reader application named IBMRss is constructed for the BlackBerry. As you go through, try to think beyond a news reader; the RSS data format can be leveraged for many other applications. You can download the full source code. Source-code snippets include:

IBMRssApplication
The application class that contains the entry point of the application.
IBMRssScreen
Class containing the UI elements, including menus and a ListField. Provides user interaction functions.
IBMRssStorage
Class that encapsulates the storage of data, including various access/helper routines.
setupdata
Method responsible for organizing the relationship between the stored data and the user interface. Used at startup and after the RSS feeds are refreshed.
loadFeed
Method that presents the entries from a specific RSS feed when selected in the UI.
showItem
Method that displays a specific RSS item's description and presents an option to view the Full Story.
RSSDescription
Class invoked by showItem to display a specific RSS item.
RssKeyListener
Class responsible for interacting with the BlackBerry's keyboard. It looks for the Enter and Esc keys.
drawListRow
Method responsible for drawing the text for the ListField, which is used to display the RSS data. A single ListField is used for displaying a collection of RSS feeds and listing the items in a specific RSS feed.
IBMRssXMLHandler
Class that extends the DefaultHandler class to handle the parsing events generated by the SAX XML parser engine, which is used to parse the RSS feeds.
IBMRssComms
Class responsible for fetching all of the RSS feeds when desired. Extends the java.lang.Thread class.
Guid
Class containing some final members useful for communicating across threads and for uniquely identifying the application's data store.
Utils
Class containing a single method of interest: split. This implements a simple tokenizer for processing data stored in our RecordStore (implemented by IBMRssStorage).

Really simple syndication

This section takes a brief look at the history of RSS, its construction, and potential uses. Once you understand the data format, you can jump headlong into building the application.

RSS basics

Really simple syndication (RSS) is an XML data structure used to convey Web content. The data format has evolved since the late 1990s as different teams worked on it. Dave Winer is credited with being instrumental in the leadership of RSS formats over the years, with contributions from teams at Netscape and others. (See Resources for historical milestones of the RSS format.) What's important is that today RSS is a fairly stable data format useful for publishing news or syndicated data.

Data that is published periodically — such as blog updates, news information, or catalog data — is ideal for the RSS format. RSS readers abound with most Internet browsers, which provide built-in support for handling RSS feeds. A typical behavior by a browser is to create a bookmark that contains a link to each item within the RSS feed. Feed is often used because RSS data is typically served by a Web server as either a static XML file delivered straight from the file system of the Web server or as a dynamically generated data source from a database on the Web server.

The next section examines the structure of RSS V2.0, which is the current specification level.

Data structure

The RSS V2.0 specification includes two required entities beneath the top-level RSS tag. There may be multiple item entities in a given RSS feed, in addition to a channel entity. Listing 1 shows an example (see Resources for more information).

Listing 1. Sample RSS feed taken from harvard.edu
<?xml version="1.0"?>
<rss version="2.0">
   <channel>
      <title>Liftoff News</title>
      <link>http://liftoff.msfc.nasa.gov/</link>
      <description>Liftoff to Space Exploration.</description>
      <language>en-us</language>
      <pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>
      <lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate>
      <docs>http://blogs.law.harvard.edu/tech/rss</docs>
      <generator>Weblog Editor 2.0</generator>
      <managingEditor>editor@example.com</managingEditor>
      <webMaster>webmaster@example.com</webMaster>
      <item>
         <title>Star City</title>
         <link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link>
         <description>How do Americans get ready to work with Russians
 	  aboard the International Space Station? They take a crash
 	  course in culture, language and protocol at Russia's <a
 	  href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm">Star
	  City</a>.</description>
         <pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
         <guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573</guid>
      </item>
      <item>
         <description>Sky watchers in Europe, Asia, and parts of Alaska
	  and Canada will experience a <a href="http://science
	  .nasa.gov/headlines/y2003/30may_solareclipse.htm">
	  partial eclipse of the Sun</a> on Saturday, May
	  31st.</description>
         <pubDate>Fri, 30 May 2003 11:06:42 GMT</pubDate>
         <guid>http://liftoff.msfc.nasa.gov/2003/05/30.html#item572</guid>
      </item>
      <item>
         <title>The Engine That Does More</title>
         <link>http://liftoff.msfc.nasa.gov/news/2003/
	  news-VASIMR.asp</link>
         <description>Before man travels to Mars, NASA hopes to design
	  new engines that will let us fly through the Solar System
	  more quickly.  The proposed VASIMR engine would do
	  that.</description>
         <pubDate>Tue, 27 May 2003 08:37:32 GMT</pubDate>
         <guid>http://liftoff.msfc.nasa.gov/2003/05/27.html#item571</guid>
      </item>
      <item>
         <title>Astronauts' Dirty Laundry</title>
         <link>http://liftoff.msfc.nasa.gov/news/2003/
	  news-laundry.asp</link>
         <description>Compared to earlier spacecraft, the International
	  Space Station has many luxuries, but laundry facilities are
	  not one of them.  Instead, astronauts have other
	  options.</description>
         <pubDate>Tue, 20 May 2003 08:56:02 GMT</pubDate>
         <guid>http://liftoff.msfc.nasa.gov/2003/05/20.html#item570</guid>
      </item>
   </channel>
</rss>

The required fields of the channel entity:

  • Title— Generally corresponds to the name of the source of the data
  • Link— A link to the Web site containing this or related information
  • Description— A brief description of the RSS feed

While the item entity does not specifically call for specific required elements, a typical subset includes:

  • Title— Title of the item
  • Link— URL for the item; often a full news story Web page
  • Description— A synopsis of the full story
  • pubDate— When this information was published

The data elements above are used by the RSS reader sample application in this tutorial.

Where to find the data

RSS feeds are found all over the Internet. If you are at a Web site and think, "It would be great to subscribe to this data," you probably aren't the first to think so. You may find a link to their RSS feeds. Many sites today also offer audio content as RSS feeds. For example, the developerWorks site and eWeek both offer many RSS feeds on a variety of topics. Handango.com, which is a large clearinghouse for mobile software applications, makes its product catalogs available as RSS feeds. On some sites, you can find the RSS links easily by looking for the image in Figure 1.

Figure 1. XML image depicting RSS feeds
XML image depicting RSS feeds

The sample in this tutorial uses RSS feeds from a handful of sources, including:

  • Ziff Davis — DevSource
  • developerWorks from IBM
  • New Yorker
  • Answers in Genesis
  • Handango BlackBerry Best Sellers

This list, a tiny fraction of the available RSS feeds on the Internet, includes tutorial lists, development topics, news stories, faith-based perspectives, and a product catalog. There are other uses for RSS data, as discussed next.

Other uses of RSS

RSS data doesn't have to be news or catalog information. It can be any kind of data as long as it fits within the confines of the data structure and the intended use of RSS data. For instance, an RSS data feed can be used to publish work orders for a mobile technician or to represent search results from a search engine. The channel data represents high-level information. The item data can provide enough details to inform the user about each individual item. The associated link field can be used to learn more.

For example, in the case of a mobile-workforce application, the title would be the job ID, the description contains a synopsis of the work ticket, and the link leads the technician back to the work-order system to get more information, as required. As you go through this tutorial, think about other types of data that can be represented in the RSS format.

It's time to construct the BlackBerry RSS Reader. If you want to build the sample application, install a version of the BlackBerry Java Development Environment if you haven't done so already.


Creating the BlackBerry RSS reader

The best way to learn is by doing, so let's get started. This section examines each of the major elements of the tutorial's sample application, including the relevant source-code snippets.

The application structure

You will create a single application piece by piece throughout the tutorial. You can download the complete source code. Figure 2 shows the source files in use in the sample application.

Figure 2. Project file in BlackBerry JDE
Project file in BlackBerry JDE

The first code snippet to review is in IBMRssApplication.java. As in any Java application, an application requires an entry point, such as main in the IBMRssApplication.java file.

Listing 2. Main method of the IBMRssApplication.java file
//
// IBMRssApplication.java
//
// MSI Services, Inc.
// Frank Ableson
// 973.448.0070
// fableson@msiservices.com
// code free to use for any purpose, commercial or otherwise

package com.msi.ibm.rssreader;

// required imports
import net.rim.device.api.ui.*;

// our application 
class IBMRssApplication extends UiApplication
{
    // applicatione entry point
    public static void main(String[] args)
    {
        // create an instance of our app
        IBMRssApplication theApp = new IBMRssApplication();
        // "run" the app
        theApp.enterEventDispatcher();
    }
    // app constructor
    public IBMRssApplication()
    {
        // create an instance of the main screen of our application
        IBMRssScreen screen = new IBMRssScreen();
        // make the screen visible
        UiApplication.getUiApplication().pushScreen(screen); 
    }
}

The main method creates a new instance of the class IBMRssApplication, which is an extension of the UiApplication class. UiApplication is found in the net.rim.device.api.ui package. The UiApplication class is a base class for all BlackBerry applications that have a UI.

The constructor of the IBMRssApplication class creates an instance of the IBMRssScreen class. This class is defined and implemented in IBMRssScreen.java. Once the instance of IBMRssScreen is created, it is passed to the pushScreen() method. This essentially brings the screen into view on the device.

Before exploring the UI of the application, there are some important aspects of the code to consider. The UI relies heavily on functions contained in other classes.

Persistent storage

Data storage and organization are crucial to many applications, and the sample application in this tutorial is no exception. The class IBMRssStorage implemented in IBMRssStorage.java is responsible for data management for the sample application. Data is stored within a RecordStore, which is found in the javax.microedition.rms package. The IBMRssStorage class has a number of helper methods for manipulating the stored records and three contained classes, which represent important constructs for the application.

Data is stored within a RecordStore as a series of random-access, variable-length byte arrays. This tutorial's sample application uses a single RecordStore containing two distinct record types: a header record that represents an RSS feed and a detail record that represents an RSS item. When RSS data is processed by the application, it is segregated into these two record types and persisted. This approach was selected because in a custom application, there may be more data elements added (or subtracted) beyond the base RSS data format used in the distribution of the data.

Storing only the data required makes the application more memory-efficient and gives it faster data access. To conserve space, all records are stored as pipe-delimited strings. The Utils class, implemented in Utils.java, contains the split method, which is used in parts of the code to facilitate manipulation of these records by parsing them and placing the individual data elements into elements of a java.util.Vector instance.

The structure of a Header record is H | Name of Feed | URL to Feed Source | Publication Date. The detail record is defined as D | Name of Feed | Title of Item | Link to Full Story | Description | Publication Date.

Listing 3 shows portions of the IBMRssStorage class responsible for opening and closing the RecordStore and a couple of helper methods for retrieving records.

Listing 3. IBMRssStorage class snippet
class IBMRssStorage 
{
    private RecordStore store;
    IBMRssStorage() 
    {
        try
        {
            store = RecordStore.openRecordStore(Guid.recordStoreName,true);
            store.setMode(RecordStore.AUTHMODE_ANY,true);
        }
        catch (Exception e)
        {
            System.err.println("error in \
            IBMRssStorage \
            constructor [" + e.getMessage() + "]");
        }
    }
    public boolean closeStore()
    {
        try
        {
            store.closeRecordStore();
        }
        catch (Exception e)
        {
            System.err.println("Error closing [" + e.getMessage() + "]");
        }
        return true;
    }
    public int getNumRecords()
    {
        try
        {
            return store.getNumRecords();
        }
        catch (Exception e)
        {
            System.err.println("Error in getNumRecords " + e.getMessage());
            return 0;
        }
    }
    public byte[] getRecord(int recId)
    {
        try
        {
            return store.getRecord(recId);
        }
        catch (Exception e)
        {
            System.err.println("Error in getRecord[" + recId + "] " + e.getMessage());
            return null;
        }
    }

Records can be enumerated according to a specific filter criterion or sort order. To enumerate records in any manner other than in an ordinal fashion, a RecordEnumeration is required. A RecordEnumeration is created by calling the enumerateRecords method of a RecordStore instance. The arguments to this method include a RecordFilter and a RecordComparator, both also part of the javax.microedition.rms package. It is common practice to define an application-specific class that implements both of these interfaces, as shown by the RssFilter class in Listing 4.

Listing 4. RssFilter class
public static class RSSFilter implements RecordFilter, RecordComparator 
    {
        private String _type = "";
        private String _name = "";
        
        RSSFilter(String type,String name)
        {
            _type = type;
            _name = name;
        }
        
        public boolean matches( byte[] recordData )
        {
            try
            {
                String oneRec = new String(recordData);
                Vector v1 = Utils.split(new String(recordData),"|");
                String recordType = (String) v1.elementAt(0);
                String recordName = (String) v1.elementAt(1);
                if (_name != null)   
                {
                    if (recordName.trim().equalsIgnoreCase(_name) &&
 			   recordType.equalsIgnoreCase(_type))
                    {
                        return true;
                    }
                }
                else
                {
                    // just matching type
                    if (recordType.equalsIgnoreCase(_type))
                    {
                        return true;
                    }
                }
            }
            catch (Exception e)
            {
                System.out.println(e);
                e.printStackTrace();
            }
            return false;
        }
        public int compare(byte[] rec1, byte[] rec2)
        {
            int comp = 0;
            try
            {
                String first = new String(rec1);
                String second = new String(rec2);
                Vector v1 = Utils.split(first,"|");
                Vector v2 = Utils.split(second,"|");
                if (_type.equals("H"))
                {
                    //compare name field
                    String r1 = ((String) v1.elementAt(1)).toUpperCase();
                    String r2 = ((String) v2.elementAt(1)).toUpperCase();
                    comp = r1.compareTo(r2);
                }
                else
                {
                    // compare title field
                    String r1 = ((String) v1.elementAt(2)).toUpperCase();
                    String r2 = ((String) v2.elementAt(2)).toUpperCase();
                    comp = r1.compareTo(r2);
                }
            }
            catch (Exception e)
            {                
            }
            if(comp < 0)
            {
                return PRECEDES;
            }
            else if( comp == 0 )
            {
                return EQUIVALENT;
            }
            else
            {
                return FOLLOWS;
            }
        }
     }

The RecordFilter interface is satisfied with the matches method. Each record must be split and reassembled to be properly matched and filtered. Sometimes a RecordFilter is employed to obtain a list of only header records and sometimes to get all records with a particular name field.

The RecordComparator is responsible for sort ordering. It employs a simple string-comparison algorithm to establish an alpha-sort implementation. Note the use of the toUpperCase method to make the sorting case-insensitive.

The IBMRssStorage class contains two additional classes: one each to represent RSS feeds and RSS items. Each class encapsulates parsing and field-level manipulations with appropriate set and get methods. The IBMRssStorage class also includes several helper, or factory, methods to aid in the creation of the IBMRssFeed and IBMRssItem classes based on a variety of input data. The classes and helpers are shown below.

Listing 5. IBMRssFeed and IBMRssItem classes
     public IBMRssFeed createFeed(String name,String url)
     {
         return new IBMRssFeed(name,url);
     }
     public IBMRssFeed createFeed(byte[] recordData)
     {
         return createFeed(new String(recordData));
     }
     public IBMRssFeed createFeed(String recordData)
     {
         Vector v = Utils.split(recordData,"|");
         IBMRssFeed feed = new IBMRssFeed();
         feed.setName((String) v.elementAt(1));
         feed.setUrl((String)v.elementAt(2));
         return feed;
     }
     public class IBMRssFeed
     {
         private String _name;
         private String _url;
         IBMRssFeed()
         {
         }
         IBMRssFeed(String name,String url)
         {
             _name = name;
             _url = url;
         }
         public String getName()
         {
             return _name;
         }
         
         public String getUrl()
         {
             return _url;
         }
          
          
         public void setName(String name)
         {
             _name = name;
         } 
         
         public void setUrl(String url)
         {
             _url = url;
         }
        
         public String toString()
         {
             String ret = "H|";
             ret += _name;
             ret += "|" + _url;
             return ret;
         }
     }
     
     public  IBMRssItem createItem(String name,String title,String link,
	  	String description,String category,String pubdate)
     {
         return new IBMRssItem(name,title,link,description,category,pubdate);
     }
     public IBMRssItem createItem(byte [] recordData)
     {
         return createItem(new String(recordData));
     }
     public  IBMRssItem createItem(String recordData)
     {
         Vector v = Utils.split(recordData,"|");
         IBMRssItem ret = new IBMRssItem();
         ret.setName((String) v.elementAt(1));
         ret.setTitle((String) v.elementAt(2));
         ret.setLink((String) v.elementAt(3));
         ret.setDescription((String) v.elementAt(4));
         ret.setCategory((String) v.elementAt(5));
         ret.setPubDate((String) v.elementAt(6));
         return ret;
     }
     public IBMRssItem createItem()
     {
        return new IBMRssItem();
     }
     public class IBMRssItem
     {
         private String _name = "";
         private String _title = "";
         private String _link = "";
         private String _description = "";
         private String _category = "";
         private String _pubDate = "";
         IBMRssItem()
         {
         }
         IBMRssItem(String name,String title,String link,
		String description,String category,String pubdate)
         {
             _name = name;
             _title = title;
             _link = link;
             _description = description;
             _category = category;
             _pubDate = pubdate;
         }
         public String getName()
         {
             return _name;
         }
         public String getTitle()
         {
             return _title;
         }
         public String getLink()
         {
             return _link;
         }
         public String getDescription()
         {
             return _description;
         }
         public String getCategory()
         {
             return _category;
         }
         public String getPubDate()
         {
             return _pubDate;
         }
         public void setName(String name)
         {
             _name = name;
         }
         public void setTitle(String title)
         {
             _title = title;
         }
         public void setLink(String link)
         {
             _link  = link;
         }
         public void setDescription(String description)
         {
             _description = description;
         }
         public void setCategory(String category)
         {
             _category = category;
         }
         public void setPubDate(String pubdate)
         {
             _pubDate = pubdate;
         }
         public String toString()
         {
             String ret = "D";
             ret += "|" + _name;
             ret += "|" + _title;
             ret += "|" + _link;
             ret += "|" + _description;
             ret += "|" + _category;
             ret += "|" + _pubDate;
             return ret;
         }
     }

The toString methods, which generate a ready-to-store representation of the record, are used. Recall that in the Java programming language, the toString method of any object may be overridden to provide a useful representation of the data. You could have chosen an alternative method name to accomplish this storage preparation step.

Now that you know where and how the data is stored, let's explore how to get it from the Internet.

Communications

The IBMRssComms class is responsible for fetching the RSS feeds from each of their respective sources on the Internet. This class extends the java.lang.Thread class so it can run independent of the UI. The "Next steps" section discusses some of the rationale for this choice, but for now, let's examine the run method.

Listing 6. IBMRssComms— Fetching data
class IBMRssComms extends Thread
{
    IBMRssComms() 
    {
    }
    public void run()
    {
        InputStream inputStream = null;
        HttpConnection httpConnection = null;
        try
        {
            // open storage
            IBMRssStorage rss = new IBMRssStorage();
            
            // grab list of feeds
            RecordEnumeration feedList = rss.getFeedList();
            
            // process each
            while (feedList.hasNextElement())
            {
                IBMRssFeed theFeed = rss.createFeed(new String(feedList.nextRecord()));

                // delete any Items under this feed
                rss.deleteFeed(theFeed.getName(),true);
                
                // connect to feed's URL
                httpConnection = (HttpConnection)Connector.open(theFeed.getUrl());
                inputStream = httpConnection.openDataInputStream();
                
                // good connection?
                if(httpConnection.getResponseCode() == HttpConnection.HTTP_OK)
                {
                    // check header field for a specific encoding
                    String desiredEncoding = "ISO-8859-1";  //iso-8859-1
                    String contenttype = httpConnection.getHeaderField("Content-Type");
                    if (contenttype != null)
                    {
                        contenttype = contenttype.toUpperCase();
                        if (contenttype.indexOf("UTF-8") != -1)
                        {
                            desiredEncoding = "UTF-8";
                        }
                    }

                    // we need an input source for the sax parser
                    InputSource is = new InputSource(inputStream);

                    // setup Encoding to match what the Web server sent us
                    is.setEncoding(desiredEncoding);
                    
                    // create the factory
                    SAXParserFactory factory = SAXParserFactory.newInstance();
        
                    // create a parser
                    SAXParser parser = factory.newSAXParser();
                    
                    // instantiate our handler
                    IBMRssXMLHandler myHandler= new IBMRssXMLHandler(theFeed);

                    // perform the synchronous parse           
                    parser.parse(is,myHandler);
                }
            }
            
            // dump feeds to debug window
            rss.dumpFeeds();
            
            // close storage
            rss.closeStore();
        }
        catch (IOException ioe)
        {
            System.err.println("IO Exception !: " + ioe.getMessage());
            ioe.printStackTrace();
        }
        catch (SAXException saxe)
        {
            System.err.println("SAX Exception !: " + saxe.getMessage());
            saxe.printStackTrace();
        }
        catch (Exception e)
        {
            System.err.println("General Error " + e.getMessage());
            e.printStackTrace();
        }
        // notify gui that we're done!
        ApplicationManager.getApplicationManager().postGlobalEvent(Guid.rssdatadone,0,0); 
    }
}

The IBMRssComms class enumerates over each available RSS feed in the RecordStore (using IBMRssStorage methods) and fetches the XML data associated with it. This data is subsequently parsed by the SAX XML parser, and the data is stored into the RecordStore appropriately. The HttpConnection class is used to obtain the data stream, and its InputStream is used to create an InputSource. This InputSource is used, along with an instance of a tutorial sample application class, to parse the XML data.

The data is parsed with the assistance of the IBMRssXMLHandler class. Interestingly, this is the only class in the entire application that truly knows anything about the underlying RSS data structure as received from the Internet source. Listing 7 contains this class, which extends the DefaultHandler class from the org.xml.sax package.

Listing 7. IBMRssXMLHandler.java class
package com.msi.ibm.rssreader;
import org.xml.sax.helpers.*;
import org.xml.sax.*;
import java.lang.StringBuffer;
import com.msi.ibm.rssreader.IBMRssStorage.*;

class IBMRssXMLHandler extends DefaultHandler
{
    StringBuffer sb = null;
    IBMRssFeed _feed = null;
    IBMRssItem item = null;
    boolean bStarted = false;
    IBMRssStorage rssStore = null;
    IBMRssXMLHandler(IBMRssFeed feed) 
    {
        _feed = feed;
        rssStore = new IBMRssStorage();
    }
    public void warning(SAXParseException e) 
    {
        System.err.println("warning: " + e.getMessage());
        bStarted = false;
    }
    public void error(SAXParseException e) 
    {
        System.err.println("error: " + e.getMessage());
    }
    public void fatalError(SAXParseException e) 
    {
        System.err.println("fatalError: " + e.getMessage());
        bStarted = false;
    }
    public void startDocument() throws SAXException
    {
    }
    public void endDocument() throws SAXException
    {
        rssStore.closeStore();
    }
    public void startElement(String namespaceURI, String localName,
		String qName, Attributes atts) throws SAXException
    {
        sb = new StringBuffer("");
        if (localName.equals("item"))
        {
            bStarted = true;
            // new item, let's set up!
            item = rssStore.createItem();
        }
    }
    public void endElement(String namespaceURI, String localName,
		String qName) throws SAXException
    {
        if (bStarted == false) return;
        if (localName.equals("item"))
        {
            item.setName(_feed.getName());
            rssStore.addRecord(item); 
        }
        if (localName.equals("title"))
        {
            item.setTitle(sb.toString());
        }
        if (localName.equals("link"))
        {
            item.setLink(sb.toString());
        }
        if (localName.equals("description"))
        {
            item.setDescription(sb.toString());
        	    }
        if (localName.equals("category"))
        {
            item.setCategory(sb.toString());
        }
        if (localName.equals("pubDate"))
        {
            item.setPubDate(sb.toString());
        }                        
        sb = new StringBuffer("");
    }
    public void characters(char ch[], int start, int length)
    {
        String theString = new String(ch,start,length);
        sb.append(theString);
    }
}

Methods in the IBMRssXMLHandler class are invoked by the SAX parsing engine as certain events occur and various tags are encountered. For example, when a startElement is encountered, a new IBMRssItem is initialized. As each element of the item is encountered, data is stored. Subsequently, when the </item> field is encountered, the class knows that a complete IBMRssItem is ready to be stored.

When the complete cycle of enumerating over each feed, fetching the underlying data source, parsing the data, and storing this data is complete, the last line of the IBMRssComms class' run method posts a global event indicating that the data update process is complete: ApplicationManager.getApplicationManager().postGlobalEvent(Guid.rssdatadone,0,0);.

The value rssdatadone is defined in the Guid class and implemented as a static final member in Guid.java. When this event is caught, as seen in the next section, the UI is updated.

All of the data is fetched, parsed, and stored, so let's return to examining the UI.

The screen

The example application has a very basic UI. The IBMRssScreen class extends the MainScreen class, which is a class provided by RIM that implements features common to BlackBerry applications. IBMRssScreen also implements the Java interface's ListFieldCallback and GlobalEventListener.The ListFieldCallback interface lets the class react to requests by the UI to draw items in a ListField control. The GlobalEventListener interface allows the UI to update after a new RSS feed has been retrieved from the Internet. Figure 3 shows the application screen when first loaded, including a few preloaded RSS feed entries.

Figure 3. IBMRssScreen showing some available RSS feeds
IBMRssScreen showing some available RSS feeds

Listing 8 contains the UI definitions and initialization code.

Listing 8. Private definitions of IBMRssScreen and the constructor
class IBMRssScreen extends MainScreen implements  ListFieldCallback, GlobalEventListener
{
    // private members - these represent the "controls"
    private LabelField statusField = null;
    private ListField feedList = null;
    private RecordEnumeration feeds = null;
    private int[] feedIds = null;
    private int[] itemIds = null;
    private int mode = 0;               // 0 is feeds, 1 is items
    private IBMRssStorage rss  = null;
    private MenuItem mnuRefreshFeeds = new MenuItem("Refresh Feeds", 100, 10) 
    {
        public void run() 
        {
            try
            {
                statusField.setText("Refreshing Feeds, Please Wait");
                feedList.setSize(0);
                IBMRssComms comms = new IBMRssComms();
                comms.start();
            }
            catch (Exception e)
            {
                System.err.println("Error Refresh Menu: " + e.getMessage());
                e.printStackTrace(); 
            }
        }
    };
    private MenuItem mnuSelectItem = new MenuItem("Select Item", 100, 10) 
    {
        public void run() 
        {
            try
            { 
                if (mode == 0)
                {
                    if (feedList.getSelectedIndex() >= 0)
                    {
                        loadFeed(feedList.getSelectedIndex());
                    }
                }
                else if (mode == 1)
                {
                    if (feedList.getSelectedIndex() >= 0)
                    {
                        showItem(feedList.getSelectedIndex());
                    }
                }
            }
            catch (Exception e)
            {
                System.err.println("Error Select Item Menu: " + e.getMessage());
                e.printStackTrace(); 
            }
        }
    };
    // constructor
    public IBMRssScreen()
    {
        // invoke the constructor of the super class (MainScreen)
        super();
        
        // give our application window a title
        setTitle("IBM Rss App");

        // setup our storage system
        rss = new IBMRssStorage();
        
        if (rss.getNumRecords() == 0)
        {
           
            IBMRssFeed myFeedDevSource = rss.createFeed("DevSource",
		 "http://feeds.ziffdavisenterprise.com/RSS/devsource.xml");
             rss.addRecord(myFeedDevSource);
            
            IBMRssFeed myFeedIBM = rss.createFeed("Developerworks",
		 "http://www.ibm.com/developerworks/views/opensource/
		 rss/libraryview.jsp");
             rss.addRecord(myFeedIBM);
            
            IBMRssFeed myFeedNY = rss.createFeed("New Yorker",
		 "http://xml.newsisfree.com/feeds/76/13276.xml");
             rss.addRecord(myFeedNY);
            
            IBMRssFeed myFeedAIG = rss.createFeed("Answers In Genesis",
		 "http://www.answersingenesis.org/store/rss/newest");
             rss.addRecord(myFeedAIG);
            
            IBMRssFeed myFeed = rss.createFeed("Handango BB Apps",
		 "http://service.handango.com/ampp/ContentRequestGenerator?
		 id=123&password=rss20content&platformId=5
		 &maxCount=50&optionId=1");
             rss.addRecord(myFeed);
        }

        // display the fields in debug window
        //rss.dumpFeeds();
        
        // create user interface components
        createui();

        // load the data
        setupdata();

        // add listeners
        addKeyListener(new RssKeyListener());
        UiApplication.getUiApplication().addGlobalEventListener(this);
    }

The two menus required by the application are defined as private class-level members of type MenuItem. After invoking the super() method to initialize the superclass and setting up a title, the persistent storage layer is set up with an instance of the IBMRssStorage class.

A few sample feeds are added if there are none found in the RecordStore. The commented-line rss.dumpFeeds() is a helper method to display the storage contents to the JDE output window. A call to the createui method adds the UI elements to the screen, and the setupdata method causes the data to be populated to the UI.

Listing 9. createui and setupdata methods
    private void createui()
    {
        try
        {
            addMenuItem(mnuRefreshFeeds);
            addMenuItem(mnuSelectItem);
              
            statusField = new LabelField("Select a Feed Below");
            add(statusField);
            add(new SeparatorField());
            feedList= new ListField();
            feedList.setCallback(this);
            add(feedList);
            
        }
        catch (Exception e)
        {
            System.out.println("Failed to create user interface components");
        }
    }

    private void setupdata()
    {
        mode = 0;
        feeds = rss.getFeedList();
        feedIds = new int[feeds.numRecords()];
        int i = 0;
        try
        {
            while (feeds.hasNextElement())
            {
                feedIds[i++] = feeds.nextRecordId();
            }
        }
        catch (Exception e)
        {
            System.err.println("Error enumerating Feeds " + e.getMessage());
        }
        statusField.setText("Select a Feed Below");
        feedList.setSize(feeds.numRecords());
        feedList.invalidate();
    }

Then the constructor adds a KeyListener and a GlobalEventListener to this screen. The KeyListener is implemented by RssKeyListener while the GlobalEventListener interface is satisfied by the IBMRssScreen class itself.

The menus are added to the screen with the addMenuItem method. The other controls are added to the MainScreen with the add method. The MainScreen implements a single VerticalManager so each added field or control simply stack beneath one another vertically.

When selected, mnuRefreshFeeds clears the ListField by setting its size to 0, then creates an instance of IBMRssComms, which is then started. While the IBMRssComms thread is executing, the UI tells the user that something is happening and to sit tight.

Figure 4. Application refreshing RSS data feeds
Application refreshing RSS data feeds

When all of the RSS feeds have been refreshed, an event is posted that's caught by IBMRssScreen's eventOccurred method. Remember, this class implements the GlobalEventListener interface.

Listing 10. GlobalEventListener interface implementation
public void eventOccurred( long guid, int data0, \
int data1, Object object0, Object object1) 
{
   if (guid == Guid.rssdatadone)
   {
        setupdata();
   }
}

Now that you have all of the data refreshed, examine how it's actually displayed on the screen in the ListField. Listing 11 shows two of the methods required by the ListFieldCallback interface.

Listing 11. ListFieldCallback interface implementation
    public void drawListRow(ListField listField,Graphics \
    graphics,int index,int y,int width)
    {
        graphics.setFont(Font.getDefault());
        if (mode == 0)
        {
            IBMRssFeed thisFeed = rss.createFeed(rss.getRecord(feedIds[index]));
            graphics.drawText(thisFeed.getName(),2,y,DrawStyle.TOP,width);
        }
        else
        {
            IBMRssItem thisItem = rss.createItem(rss.getRecord(itemIds[index]));
            graphics.drawText(thisItem.getTitle(),2,y,DrawStyle.TOP,width);
        }
    }

    public int getPreferredWidth(ListField listField)    
    {
        return Graphics.getScreenWidth();
    }

The getPreferredWidth method is fairly straightforward; it is only concerned with how wide the list should be drawn. The real work takes place in the drawListRow method, which uses the arguments passed to determine which data to supply. In the sample application, the private member variable named mode determines whether the ListField is displaying the feeds or the items for a particular feed. You can see this clearly in drawListRow as the method retrieves the appropriate data element and uses the graphics instance to draw the text with the drawText method.

There are two arrays of integers used to cache the record IDs for quick access to a particular record. The arrays are updated each time a new RSS feed is selected. Code to manage the feedIds is found in the setupdata method. The code to manage the itemIds is in the loadFeed method.

Listing 12. loadFeed method caches record IDs
   private void loadFeed(int feedIndex)
    {
        IBMRssFeed thisFeed = rss.createFeed(rss.getRecord(feedIds[feedIndex]));

        statusField.setText(thisFeed.getName());

        RecordEnumeration items = rss.getFeedItems(thisFeed.getName());
        itemIds = new int[items.numRecords()];
        int i = 0;
        try
        {
            while (items.hasNextElement())
            {
                itemIds[i++] = items.nextRecordId();
            }
        }
        catch (Exception e)
        {
            System.err.println("Error enumerating items in feed [" +
		 thisFeed.getName() + "] " + e.getMessage());
        }
        mode = 1;
        if (items.numRecords() > 0)
        {
            feedList.setSelectedIndex(0);
        }
        feedList.setSize(items.numRecords());
        feedList.invalidate();
    }

When an item is selected, the showItem method is invoked, which creates an instance of IBMRssDescription, which is a contained class of IBMRssScreen. Figure 5 shows this screen in action.

Figure 5. Selecting an item from a feed
Selecting an item from a feed

When Full Story is selected, the item's link is opened in the BlackBerry browser with a single line of code: Browser.getDefaultSession().displayPage(_item.getLink());.

Running the application

At this point, you've reviewed all of the important code snippets, and it's time to build and test the application. If you're new to BlackBerry development and need some help building an application in the JDE, see Resources.

Assuming the application has been built without errors, you can run the application in the BlackBerry simulator:

  1. Make sure the MDS Simulator is running. The MDS simulator allows the BlackBerry simulator to connect to the network, including the Internet.
  2. Selecting the F5 key will start the BlackBerry simulator.
  3. The tutorial sample application will not start right away. To start it, navigate to the application's icon on the home-page application ribbon and select the tutorial sample application, named IBMRssReader, with the RSS icon as shown below. The arrow keys on the computer simulate the track wheel, the Enter key simulates pressing the track wheel, and the Esc key emulates the BlackBerry Esc button.
Figure 6. IBMRssReader on the BlackBerry simulator
IBMRssReader on the BlackBerry simulator

The application is built! Feel free to run the application and experiment with different RSS feeds.


Next steps

There are a couple of additional items before wrapping up.

Error handling

Error handling is omitted from this tutorial for the sake of brevity and clarity. Of course, any production-ready code should have a healthy dose of error handling and instructions for the user in case something goes awry.

Out of necessity, error handling has been included in the IBMRssXMLHandler class. Note the implementation of the error and fatalerror methods. If these methods are not implemented by our handler and an error is encountered, the entire parse process will fail. In this case, any items that have invalid data will simply be ignored in the feed. The application can continue on and process the data as best it can without troublesome fatal errors that result in losing all of the data feed when there was perhaps only a single offending entry.

Automatic updates

The IBMRssComms class was implemented as an extension of java.lang.Thread. For a production-ready RSS reader, it's desirable to have the data updated on a schedule or in an automated fashion. When you get out of bed in the morning and reach for your BlackBerry, you can have the latest news and information in your RSS reader. You can turn the application into a system module and set a timer to invoke the IBMRssComms class at a convenient hour. By implementing this class as a thread, it can do its work in the background without interfering with other uses of the application.


Summary

In this tutorial, you explored the RSS data format and created a BlackBerry application to process, manage, and display the data for applications as simple and useful as a new reader. Alternative uses for RSS data and your BlackBerry were also suggested. You learned about fundamental BlackBerry development topics, including HTTP data retrieval, XML parsing, and record storage and retrieval. You also saw how the ListField can be a flexible data-representation construct. Leveraging the open source technology of Java technology and RSS has great potential for bringing value to vertical business and consumer applications.


Download

DescriptionNameSize
RSS reader source codeos-blackberry2-IBMRssReader_src.zip113KB

Resources

Learn

  • Read Part 1 of this series, which lays the groundwork for an open source data-collection application upon which an accessible and easy-to-use data collection service is built.
  • BlackBerry Desktop Software: Research In Motion (RIM) offers a full range of helpful user and administrator information on BlackBerry Desktop Software, including more about installing software via the Desktop Manager.
  • You'll find articles about every aspect of Java programming in the developerWorks Java technology zone.
  • OpenSource.org presents a catalog of the most popular open source licenses.
  • Learn more about the history of RSS from Harvard Law.
  • To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
  • Stay current with developerWorks' Technical events and webcasts.
  • Follow developerWorks on Twitter.
  • Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
  • Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
  • Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=376403
ArticleTitle=Create BlackBerry applications with open source tools, Part 2: Building an RSS reader
publish-date=02172009