Mobile Zone is brought to you in partnership with:

Software Engineer And co-founder of nerdability.com. Having spent 6 years in a leading tech consultancy I am now working in startups in London. Interested in java, groovy, spring, js, android and anything else that looks fun. Rob has posted 6 posts at DZone. You can read more from them at their website. View Full User Profile

Building an RSS Reader for Android

04.10.2013
| 36627 views |
  • submit to reddit

This tutorial will walk through building an RSS reader on the Android platform (focusing on 3.0 + as it will be using Fragments). All the code is available as a complete, working Android app that you can fork/download and fire up straight away on a compatible Android device/emulator. So feel free to go grab that from GitHub before continuing. 

It is not an unusual requirement for mobile apps these days to be able to consume an RSS feed from another site (or many) to aggregate the content -  Or maybe you just want to build your own Android app now that Google has announced it will be retiring Reader.

Those of you who have worked with RSS in other JVM languages will know that there are plenty of libraries available that can handle parsing RSS - however, because the android platform doesn't actually contain all the core java classes, almost all of the RSS libraries have not been supported.

Fear not though, as Java's SAX parser is available, so with a bit of code we can get a custom RSS parser up and running in no time!

This walk through will cover off the basics of getting an RSS reader app up and running quickly and will also cover some details of Android's fragment system for tablet optimization as well as some things to watch out for (such as changes in the platform that mean network operations cannot be run in the main thread, which requires some tweaking if you have worked on earlier versions).

All the code for the app is also available on our GitHub so feel free to fork that and try loading it up on your Android device/emulator.


Parsing an RSS Feed:

So to get started we will look at parsing the feed - if you have any experience parsing XML using SAX in Java then you will know how easy this is. All we need to do is to tell the parser which XML nodes we care about, and what to do with them.
If  you have never implemented a SAX parser before, there are three primary methods that we will override: 
  • startElement() - This is called by the parser every time a new XML node is found
  • endElement() - This is called by the parser every time an XML node is closed (e.g. </.. )
  • chars() - this is called when characters are found between nodes
public void startElement(String uri, String localName, String qName, Attributes atts) {
  	chars = new StringBuffer();
}
 
public void characters(char ch[], int start, int length) {
  	chars.append(new String(ch, start, length));
}
 
public void endElement(String uri, String localName, String qName) throws SAXException {
  	if (localName.equalsIgnoreCase("title")){
			currentArticle.setTitle(chars.toString());
		} else if (localName.equalsIgnoreCase("description")){
			currentArticle.setDescription(chars.toString());
		} else if (localName.equalsIgnoreCase("published")){
			currentArticle.setPubDate(chars.toString());
		} else if (localName.equalsIgnoreCase("id")){
			currentArticle.setGuid(chars.toString());
		} else if (localName.equalsIgnoreCase("author")){
			currentArticle.setAuthor(chars.toString());
		} else if (localName.equalsIgnoreCase("content")){
			currentArticle.setEncodedContent(chars.toString());
		} else if (localName.equalsIgnoreCase("entry")){
	} 
 
	// Check if looking for article, and if article is complete
	if (localName.equalsIgnoreCase("entry")) {
		articleList.add(currentArticle);
		currentArticle = new Article();
 
		// Lets check if we've hit our limit on number of articles
		articlesAdded++;
		if (articlesAdded >= ARTICLES_LIMIT){
			throw new SAXException();
		}
  }
}
Because we only really care about capturing data from the leaf nodes, our startElement() method is left empty. The chars() element has to be watched, as there is no guarantee when it will be called (e.g. in a node like hello world  this method might be called several times between the start and end) so every time we will just append the contents to a StringBuffer - that way we can be sure that we will have captured all the data in the node.  By the time the endElement() method is called, we know that we have the contents of the node itself, and we just have to store the data.  At this point, we just quickly knocked up a POJO with the attributes that we wanted to capture - the Strings that we match on are the node names from the ATOM RSS feed (that Blogger uses) - if you are using another feed, just have a quick look at the feed and update the node names appropriately.

Using our Feed in an Android App

So, that was easy right? Once that parser has run through (and you could use that code standalone in any java app really) then you will have a list of Java objects that have the core details about the latest blog posts on the feed (title, author, datecreated, content, etc) - So now lets look at using it in an Android app.

We will assume a basic understanding of the Android SDK and the concept of Fragments, so won't go back to basics with that stuff.

What we will do, is create a basic ListFragment and an RSSService class that we will use to populate the list. In our ListFragment we will simply tell our RSS service to populate the list:
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    refreshList();
}
 
private void refreshList(){
  rssService = new RssService(this);
  rssService.execute(BLOG_URL);
}

Simple, right?

Let's take a look at what our helpful RSS service is doing for us.

public class RssService extends AsyncTask<String, Void, List<Article>> {
 
  private ProgressDialog progress;
	private Context context;
	private ArticleListFragment articleListFrag;
 
	public RssService(ArticleListFragment articleListFragment) {
		context = articleListFragment.getActivity();
		articleListFrag = articleListFragment;
		progress = new ProgressDialog(context);
		progress.setMessage("Loading...");
	}
 
 
	protected void onPreExecute() {
		Log.e("ASYNC", "PRE EXECUTE");
		progress.show();
	}

The first thing to note is that this class is extending Android's AsyncTask- The reason for this is that since Android 3.0, you are no longer able to perform network operations in the main application thread, so being as our class is going to have to fetch some RSS feeds we are going to have to spin off a new thread.

As you can see, the constructor just sets some context info that we will use later on, and then builds a progress dialogue - this is then displayed in the onPreExecute() method - This lets us show a "loading" spinning disk whilst we fetch the data.

Android's AsyncTask's primary method that handles the actual work that you want to do asynchronously is called "doInBackground()" - In our case, this is simple - we just invoke our SAX RSS parser and fetch our feed data:

@Override
protected List<Article> doInBackground(String... urls) {
	String feed = urls[0];
	URL url = null;
	try {
 
		SAXParserFactory spf = SAXParserFactory.newInstance();
		SAXParser sp = spf.newSAXParser();
		XMLReader xr = sp.getXMLReader();
 
		url = new URL(feed);
		RssHandler rh = new RssHandler();
 
		xr.setContentHandler(rh);
		xr.parse(new InputSource(url.openStream()));
    
    ...

Finally, we will override the "onPostExecute()" method of the async class to use our newly fetched list to populate our ListFragment.  You note how when we overrode the doInBackground() method earlier we set the return to List of Articles (where Article is my simple POJO containing my RSS blog post info) - well this must correspond to the argument of the "onPostExecute()" method, which looks like this:

protected  void onPostExecute(final List<Article>  articles) {
	Log.e("ASYNC", "POST EXECUTE");
	articleListFrag.getActivity().runOnUiThread(new Runnable() {
		@Override
		public void run() {
			for (Article a : articles){
				Log.d("DB", "Searching DB for GUID: " + a.getGuid());
				DbAdapter dba = new DbAdapter(articleListFrag.getActivity());
				dba.openToRead();
				Article fetchedArticle = dba.getBlogListing(a.getGuid());
				dba.close();
				if (fetchedArticle == null){
					Log.d("DB", "Found entry for first time: " + a.getTitle());
					dba = new DbAdapter(articleListFrag.getActivity());
					dba.openToWrite();
					dba.insertBlogListing(a.getGuid());
					dba.close();
				}else{
					a.setDbId(fetchedArticle.getDbId());
					a.setOffline(fetchedArticle.isOffline());
					a.setRead(fetchedArticle.isRead());
				}
			}
			ArticleListAdapter adapter = new ArticleListAdapter(articleListFrag.getActivity(), articles);
			articleListFrag.setListAdapter(adapter);
			adapter.notifyDataSetChanged();
		}
	});
	progress.dismiss();
}

Actually, all we really needed to do in this method would be pass our new List or articles to the ListFragment and notify it of the change as below:

ArticleListAdapter adapter = new ArticleListAdapter(articleListFrag.getActivity(), articles);
articleListFrag.setListAdapter(adapter);
adapter.notifyDataSetChanged();

However, in our application we have added a bit more sugar on the app - and we have actually backed the app with a simple DB that records the unique IDs of the posts and tracks whether or not they have been read to provide a nicer highlighting of listed blog posts.

So that's it - there's plenty more you can add to your RSS reader app, such as storing posts for reading later, and supporting multiple feeds/feed types - but feel free to fork the code on GitHub, or just download on to your android device to enjoy all our NerdAbility updates! 

Application running in Andriod emulatorRSS application running in an Android tablet emulator
Published at DZone with permission of its author, Rob Hinds. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Michał Moczulski replied on Wed, 2013/04/10 - 12:39pm

How do you handle events such as screen rotation ? Currently you're using AsyncTask which will be recreated when user rotate the screen and downloading and parsing process will start again. I think better option is to use e.g. IntentService for this purpose. 

RssService is responsible for parsing data and querying the database I think you can split this into two different task. What is the advantage of this approach ? When your app has some content stored in database, data can be displayed and user don't have to wait for the end of the parsing process(which can take a while).

Rob Hinds replied on Wed, 2013/04/10 - 2:08pm

Your absolutely right Michal - I originally wrote the post as an introductory overview to help people get past the parsing of an RSS feed (the question kept coming up on StackOverflow), but you are correct, not re-downloading on rotation would be better!

 Would also be better design to split out the services for RSS and db operations (otherwise it breaks SRP).

Peterfreeman Osama replied on Sun, 2014/01/12 - 10:06am

 Great blog! I really love how it is easy on my eyes and the information are well written. I am wondering how I might be notified whenever a new post has been made. I have subscribed to your rss feed which really should do the trick! Have a nice day!

customer service skills listhr source consultinghuman resources internships superior staffingproject proposal template

Keerthan Rao replied on Tue, 2014/09/30 - 1:16am

 Hi.. I just wanted some technical help from you guys that..How to retrieve rss feeds from website.. For example user inputs "NewYork Times".. App should retrieve  rss feeds from user input. Is it possible.. Please help.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.