Showing posts with label rss reader. Show all posts

Android RSS Reader 2.0

Well, its now 2013, and I have updated a version of this post - a long with a complete working application on GitHub - you can find that here it covers Android 3.0+ so includes fragments and parsing the RSS in an AsyncTask. Of course, you can still read this post and access the code for the pre-3.0 version!

Since posting a link to my simple RSS Reader for Android implemented using a custom SAX parser I have some questions regarding upgrading to handle images - I have previously posted on how to implement new tags in the feed that you want to handle (including nested tags) - but one question I received was regarding when images are just embedded in the text (e.g. not a specific image tag, but say as part of a description as HTML).

So, I have updated the code (uploaded, see the sidebar) - this time it parses the description text data to retrieve any Image links its pretty grim string manipulation, but it does the job!


As you can see, it now parses the image URL from the description and inserts it to the left of the feed item.
What did I change?

First I needed to change the Article class to parse the link. I updated the setDescription method to also check for IMAGE tags to parse, if any found then it would set the ImgLink member:

 public void setDescription(String description) {
  this.description = description;
  
  //parse description for any image or video links
  if (description.contains("<img ")){
   String img  = description.substring(description.indexOf("<img "));
   String cleanUp = img.substring(0, img.indexOf(">")+1);
   img = img.substring(img.indexOf("src=") + 5);
   int indexOf = img.indexOf("'");
   if (indexOf==-1){
    indexOf = img.indexOf("\"");
   }
   img = img.substring(0, indexOf);
   
   setImgLink(img);
   
   this.description = this.description.replace(cleanUp, "");
  }
 }



I then also had to update the RssListAdapter class - this needed the necessary action to pull down the link based on our parsed URL and then inflate the ImageView in our row:
         if (jsonImageText.get("imageLink") != null){
          String url = (String) jsonImageText.get("imageLink");
                URL feedImage= new URL(url);
             
             HttpURLConnection conn= (HttpURLConnection)feedImage.openConnection();
                InputStream is = conn.getInputStream();
                Bitmap img = BitmapFactory.decodeStream(is);
                imageView.setImageBitmap(img);
         }

Android RSS Tutorial Revisited - Some Questions Asked..

I was going to write a post about unit testing Spring web apps, but I have had a few questions recently about the Android RSS parser that I posted a while back, so I thought I should answer those quickly now (the explanation seemed a little long winded to add as a comment).

The questions were specifically about how to extend the example code provided to parse additional tags. These questions are actually just core SAX questions, and so the answer is basically more detail about SAX and can actually be applied to non RSS/Android parsers as well.

The two questions I wanted to tackle was
1) How to extend the code to handle additional tags
2) How to extend the code if there are tags that should be processed inside other tags (for example, an tag inside the tag

Both tasks require an understanding of the three core methods in the SAX Parser:
- startElement() – this is called every time an opening XML tag is found of any nature
- endElement() – This is called every time a closing XML tag is found of any nature
- characters() – this is called between start and end tags, but not necessarily at the end of the text being processed (this is very important to remember!)

In our very simple example, we are assuming that we are only ever interested in the lowest level leaf nodes of the XML feed, and they will only ever contain text. From this assumption, in the startElements() method, we don’t need to process which tag we are in, we can just reset the StringBuffer (the StringBuffer is used to gather text in between tags). By resetting the buffer every time we start an element, we know that when we close an element that we are interested in (as it is always only the leaf node) we have the text contents of that node in the buffer.

If you inspect the endElements() method from the original example, you will a nested IF block- this block examines the name of the tag being closed and decides if we are interested in the content of the current closing node. If we are interested, then we take the text from the buffer and save it in our context object, otherwise we just ignore it, and the buffer will be cleared when the next xml node is opened.

Therefore, to extend just to add another node, we can just add another condition to the IF block with the name of the new tag. Easy!
public void endElement(String uri, String localName, String qName) throws SAXException {

        if (localName.equalsIgnoreCase("title"))
        {
            Log.d("LOGGING RSS XML", "Setting article title: " + chars.toString());
            currentArticle.setTitle(chars.toString());

        }
        else if (localName.equalsIgnoreCase("description"))
        {
            Log.d("LOGGING RSS XML", "Setting article description: " + chars.toString());
            currentArticle.setDescription(chars.toString());
        }
        else if (localName.equalsIgnoreCase("the name of any new node goes here!"))
        {
            //here you can handle the node contents as you like.. chars.toString() will give you all
            //the contents of the node


The second issue is a little more challenging, as it requires some additional processing, and we have to turn to our startElement() method.

Lets imagine we are processing an XML that looks as follows:

    
        example feed title
        16/02/2011
        example description of feed, and here is a picture http://example.com/image.jpg i hope you like the feed!
        
    


The title and date are processed by our existing code easily, as when the parser reaches the opening title or pubDate tag it clears the buffer, and then when it reaches the closing tag for each node, it checks the IF block and saves the text in the node into our context object.

The description tag is more complex, as we want to keep the text, but currently, as soon as the inner img tag is reached by our parser, the buffer is cleared, and this clears all the description text we have up until that point.

To handle this scenario, the easiest way is to process the opening node in the startElement() method, and have a secondary buffer that is used specifically for the description node – this way, when we open the description node, we reset the description buffer, but it will not get reset at the img node, then in the endElement() block we add the condition to handle the closing of the tag. The full code to handle the above scenario is below: (note, we also need to update our characters() method to consume the text between the nodes – for performance, I have set a flag so it knows only to write to our new description specific buffer when inside the node)

public class RSSHandler extends DefaultHandler {

    // Feed and Article objects to use for temporary storage
    private Article currentArticle = new Article();
    private List
articleList = new ArrayList
(); // Number of articles added so far private int articlesAdded = 0; // Number of articles to download private static final int ARTICLES_LIMIT = 15; //Current characters being accumulated StringBuffer chars = new StringBuffer(); /** * THIS IS A NEW BUFFER SPECIFICALLY FOR DESCRIPTION **/ StringBuffer descriptionChars = new StringBuffer(); boolean processingDescription = false; /* * This method is called everytime a start element is found (an opening XML marker) * here we always reset the characters StringBuffer as we are only currently interested * in the the text values stored at leaf nodes * * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) */ public void startElement(String uri, String localName, String qName, Attributes atts) { chars = new StringBuffer(); //IF DESCRIPTION THEN SET FLAG TO START PROCESSING SPECIFIC BUFFER if (localName.equalsIgnoreCase("description")) { descriptionChars = new StringBuffer(); processingDescription = true; } } /* * This method is called everytime an end element is found (a closing XML marker) * here we check what element is being closed, if it is a relevant leaf node that we are * checking, such as Title, then we get the characters we have accumulated in the StringBuffer * and set the current Article's title to the value * * If this is closing the "Item", it means it is the end of the article, so we add that to the list * and then reset our Article object for the next one on the stream * * * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) */ public void endElement(String uri, String localName, String qName) throws SAXException { if (localName.equalsIgnoreCase("title")) { Log.d("LOGGING RSS XML", "Setting article title: " + chars.toString()); currentArticle.setTitle(chars.toString()); } else if (localName.equalsIgnoreCase("description")) { /** * ALSO PROCESS HERE THE DECRIPTION SPECIFIC STRING BUFFER **/ Log.d("LOGGING RSS XML", "Setting article description: " + descriptionChars.toString()); currentArticle.setDescription(descriptionChars.toString()); //ALSO SET DESCRIPTION FLAG TO FALSE processingDescription = false; } else if (localName.equalsIgnoreCase("pubDate")) { Log.d("LOGGING RSS XML", "Setting article published date: " + chars.toString()); currentArticle.setPubDate(chars.toString()); } else if (localName.equalsIgnoreCase("encoded")) { Log.d("LOGGING RSS XML", "Setting article content: " + chars.toString()); currentArticle.setEncodedContent(chars.toString()); } else if (localName.equalsIgnoreCase("item")) { } /** * THIS IS NEW TO HANDLE IMAGE TAG **/ else if (localName.equalsIgnoreCase("img")) { Log.d("LOGGING RSS XML", "Setting article description image: " + chars.toString()); currentArticle.setDescription(chars.toString()); } else if (localName.equalsIgnoreCase("link")) { try { Log.d("LOGGING RSS XML", "Setting article link url: " + chars.toString()); currentArticle.setUrl(new URL(chars.toString())); } catch (MalformedURLException e) { Log.e("RSA Error", e.getMessage()); } } // Check if looking for article, and if article is complete if (localName.equalsIgnoreCase("item")) { 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(); } } } /* * This method is called when characters are found in between XML markers, however, there is no * guarante that this will be called at the end of the node, or that it will be called only once * , so we just accumulate these and then deal with them in endElement() to be sure we have all the * text * * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) */ public void characters(char ch[], int start, int length) { chars.append(new String(ch, start, length)); if (processingDescription){ descriptionChars.append(new String(ch, start, length)); } } /** * This is the entry point to the parser and creates the feed to be parsed * * @param feedUrl * @return */ public List
getLatestArticles(String feedUrl) { URL url = null; try { SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); url = new URL(feedUrl); xr.setContentHandler(this); xr.parse(new InputSource(url.openStream())); } catch (IOException e) { Log.e("RSS Handler IO", e.getMessage() + " >> " + e.toString()); } catch (SAXException e) { Log.e("RSS Handler SAX", e.toString()); } catch (ParserConfigurationException e) { Log.e("RSS Handler Parser Config", e.toString()); } return articleList; } }

Android RSS Reader Tutorial

Well, its now 2013, and I have updated a version of this post - a long with a complete working application on GitHub - you can find that here it covers Android 3.0+ so includes fragments and parsing the RSS in an AsyncTask. Of course, you can still read this post and access the code for the pre-3.0 version!

There are two core classes that I used in my RSS parsing project - The RssHandler (extendes the SAX DefaultHandler) and the Article object (I use this to store all the information about an article/item in an RSS stream.


Here I will go through the SAX Handler implementation.


First we declare global variables we will use, most of these are self-explanatory: currentArticle stores all the information about the current RSS item being processed; articleList stores a list of all items processed so far; the two counters then count the number of RSS items processed and the limit (you will want to set this to the number of articles you want to fetch, as the stream could be very big); the characters StringBuffer, we use this to accumulate the text in each simple element:

// Feed and Article objects to use for temporary storage
 private Article currentArticle = new Article();
 private List
articleList = new ArrayList
(); // Number of articles added so far private int articlesAdded = 0; // Number of articles to download private static final int ARTICLES_LIMIT = 15; //Current characters being accumulated StringBuffer chars = new StringBuffer();



When implementing the SAX DefaultHandler you need three core methods: startElement(), endElement(), characters().

This is the startElement() method, this is called on every opening XML node (such as <item>). In our case all we want to do is reset our chars StringBuffer to be sure that the text we retrieve is always only from our current simple element
public void startElement(String uri, String localName, String qName, Attributes atts) {
  chars = new StringBuffer();
 }



Next we have characters() method - this is called whilst reading the text stored in a simple element - however, this is not just called once at the end of the element, but can be called several times, so we must be careful to be sure we dont process the text here as it maybe incomplete - so for now we just accumulate the text in our String Buffer, and we will process it later, when we ar sure we have all the text:
public void characters(char ch[], int start, int length) {
  chars.append(new String(ch, start, length));
 }


Finally we have the endElement() method - this is called when any closing XML marker is found (for example, </item>). At this point we check which element we are in and decide if we should process the contents. For example, if we have found </title> then we know we are closing the <title> simple element - we know our string buffer was reset in the startElement for <title>, and we know our characters() method has been called and collected all the text insde this element, so we can now safely use this information to set the title on our currentArticle object:
public void endElement(String uri, String localName, String qName) throws SAXException {

  if (localName.equalsIgnoreCase("title"))
  {
   Log.d("LOGGING RSS XML", "Setting article title: " + chars.toString());
   currentArticle.setTitle(chars.toString());

  }
  else if (localName.equalsIgnoreCase("description"))
  {
   Log.d("LOGGING RSS XML", "Setting article description: " + chars.toString());
   currentArticle.setDescription(chars.toString());
  }
  else if (localName.equalsIgnoreCase("pubDate"))
  {
   Log.d("LOGGING RSS XML", "Setting article published date: " + chars.toString());
   currentArticle.setPubDate(chars.toString());
  }
  else if (localName.equalsIgnoreCase("encoded"))
  {
   Log.d("LOGGING RSS XML", "Setting article content: " + chars.toString());
   currentArticle.setEncodedContent(chars.toString());
  }
  else if (localName.equalsIgnoreCase("item"))
  {

  }
  else if (localName.equalsIgnoreCase("link"))
  {
   try {
    Log.d("LOGGING RSS XML", "Setting article link url: " + chars.toString());
    currentArticle.setUrl(new URL(chars.toString()));
   } catch (MalformedURLException e) {
    Log.e("RSA Error", e.getMessage());
   }

  }




  // Check if looking for article, and if article is complete
  if (localName.equalsIgnoreCase("item")) {

   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();
   }
  }


This Handler will allow us to parse an RSS stream and create a list of Article objects for later processing. As mentioned earlier, the entire Android project can be downloaded here (its an eclipse project) where you can see the entire of this class and the rest of the code plugged together as a very simple RSS reader

Android and RSS

Recently, I was working on a simple Android application for an upcoming independent singer - the concept was simple, it was going to be an app with three different pages:

  1. Latest News - this was going to just be an RSS feed of the artists site to get all the latest info
  2. Watch Videos - this was going to be the artists youtube channel embeded
  3. Latest Tweets - this was going to be the artist's tweets (like any good upcoming artist they were making full use of social media channels)

Embedding youtube into android was going to be straight forward (both Google), and from my experience creating Zippy, my Twitter application, the tweets list was going to be easy, so the only work really was to create the RSS feed. Easy I thought..


A quick Google for Java RSS libraries threw up ROME - so I went about constructing all the necessary parts so ROME could plugin and feed me the info i needed to create a JSON list for my list view. However, on plugging it all together and trying to get ROME to work on my Android emulator it just wasn't playing nicely, so again back on Google and I found this discussion on StackOverflow - it turns out that ROME along with some other RSS parsers don't work on Android on account of incompatible packages on the Dalik JVM.


Being fairly frustrated at this point I decided to go back to basics and just implement my own RSS parser based on SAX - I found a tutorial here on a complete RSS parser Android application, so I was able to use some of that for the boiler plate stuff, but there were some issues with the parser not working correctly (not correctly handling the "characters" values in endElement()) but I have managed to get it working and creating a simple Android RSS reader - you can get the complete code here: (also on my source code example list to the left)

The code is an Eclipse project, you can just import this into your workspace and (as long as Android is correctly set up) run the application and you will see a simple RSS stream from my blog!