Monthly Archives: March 2012

Reading RSS content in Android

Spanish

The below document describes how to read RSS feeds in Android. The below example will read a RSS file and the contents will be displayed in a ListView.  I won’t cover how to code the ListView component, but the explanation can be found in one of my previous posts.

The RSS format is basically a XML file with a defined structure. In Java, there are multiple ways to read a XML document (SAX, DOM, STAX, etc). The below code uses the XmlPullParser class to read the XML; this class is similar to STAX (Android does not come with this API) and provide an easy way to parse xml data. Finally, the below code is based on an article posted by IBM.

The following libraries and applications are used:

– Windows 7

– Eclipse Galileo (Android DDMS, Android Development Tools)

1.1) First, create your Android project:

image

1.2) You can put any name to the project, in my case, I called it RSSFeeder. The android version specified is 2.2, but the code should work correctly on later versions. In the “Properties” section, you must provide the application name, package name and the activity name. These fields were filled out as follows: RSSFeeder, com.expertnotfound.rssfeed, RSSMain.

If you want to create an Android test project, click on NEXT, otherwise select FINISH.

1.3) Eclipse automatically creates a skeleton and updates the AndroidManifest file. Next, you must update the application layout (res->layout->main.xml) and add the objects we need for the ListView (ImageView & TextView):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="6dip">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">
        <TextView android:id="@+id/mLine1"
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:gravity="center_vertical"/>
        <TextView android:id="@+id/mLine2"
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:singleLine="true"
            android:ellipsize="marquee"/>
    </LinearLayout>
</LinearLayout>

1.4) As previously said, the RSS format is based on a defined set of XML tags. For this example, we will be working with the following tags: title, link, description and date (Check out wikipedia if you want more details on the RSS format). The content of these tags will be stored in an object called Message and it will be defined as follows:

image

package helpers;

import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Message implements Comparable<Message>{
	static SimpleDateFormat FORMATTER = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
    private String title;
    private URL link;
    private String description;
    private Date date;
	/**
	 * @return the title
	 */
	public String getTitle() {
		return title;
	}
	/**
	 * @param title the title to set
	 */
	public void setTitle(String title) {
		this.title = title;
	}
	/**
	 * @return the link
	 */
	public URL getLink() {
		return link;
	}
	/**
	 * @param link the link to set
	 */
	public void setLink(String link) {
		try {
			this.link = new URL(link);
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/**
	 * @return the description
	 */
	public String getDescription() {
		return description;
	}
	/**
	 * @param description the description to set
	 */
	public void setDescription(String description) {
		this.description = description;
	}
	/**
	 * @return the date
	 */
	public String getDate() {
		return FORMATTER.format(this.date);
	}
	/**
	 * @param date the date to set
	 */
	public void setDate(String date) {
		// pad the date if necessary
        while (!date.endsWith("00")){
            date += "0";
        }
        try {
            this.date = FORMATTER.parse(date.trim());
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
	}

      //OPTIONAL
    public int compareTo(Message another) {
        if (another == null) return 1;
        return another.date.compareTo(date);
    }
}

As you can see, this class implements the Comparable interface. It becomes useful if you want to sort your items by title, description or anything else other than date (the RSS feeds already come sorted by date) .

1.5) The next step is the parser class. For this task, we will implement 2 classes; the first one, is an abstract class that will contain the XML tags that we will be reading and the methods that are expected by any RSS parser. The second class, will extend the abstract class and will do all the parsing.

The abstract class is named BaseFeedParser and is defined as follows:

image

Code:

package helpers;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

public abstract class BaseFeedParser {
	//XML TAGS
	static final String T_PUB_DATE = "pubDate";
	static final String T_DESCRIPTION = "description";
	static final String T_LINK = "link";
	static final String T_TITLE = "title";
	static final String T_ITEM = "item";
	static final String T_CHANNEL = "channel";

	List<Message> message;
	private URL feedUrl;

    protected BaseFeedParser(String feedUrl){
        try {
        	this.feedUrl = new URL(feedUrl);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

	protected InputStream getInputStream()
	{
		try
		{
			return this.feedUrl.openConnection().getInputStream();
		}catch(Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}
}

The constructor will receive as input the URL for the RSS feed and the getInputStream method will open the HTTP connection and will return the InputStream that will be used by the XmlPullParser object.

1.6) Next, we will code our parser. Add a new class to the project and call it MyCustomFeedParser:
image
Code:
package helpers;

import java.util.ArrayList;
import java.util.List;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;

public class MyCustomFeedParser extends BaseFeedParser {

	public MyCustomFeedParser(String feedUrl) {
		super(feedUrl);
		// TODO Auto-generated constructor stub
	}

	public List<Message> parse() {
        List<Message> messages = null;

        XmlPullParserFactory factory = null;
        XmlPullParser parser = null;

        try {
        	factory = XmlPullParserFactory.newInstance();
        	factory.setNamespaceAware(false);
        	parser = factory.newPullParser();

            // auto-detect the encoding from the stream
            parser.setInput(this.getInputStream(), null);
            //if it fails, the encoding can be passed as input: parser.setInput(this.getInputStream(), "UTF-8");

            //what is my event type? start tag, end tag, etc
            int eventType = parser.getEventType();

            Message currentMessage = null;
            boolean done = false;

            while (eventType != XmlPullParser.END_DOCUMENT && !done){

                String name = null;
                switch (eventType){
                    case XmlPullParser.START_DOCUMENT:
                        messages = new ArrayList<Message>();
                        break;
                    case XmlPullParser.START_TAG:
                    	name = parser.getName();
                        if (name.equalsIgnoreCase(T_ITEM)){
                            currentMessage = new Message();
                        } else if (currentMessage != null){
                            if (name.equalsIgnoreCase(T_LINK)){
                                currentMessage.setLink(parser.nextText());
                            } else if (name.equalsIgnoreCase(T_DESCRIPTION)){
                            	currentMessage.setDescription(parser.nextText());
                            } else if (name.equalsIgnoreCase(T_PUB_DATE)){
                                currentMessage.setDate(parser.nextText());
                            }
                            else if (name.equalsIgnoreCase(T_TITLE)){
                                currentMessage.setTitle(parser.nextText());
                            }
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        name = parser.getName();
                        if (name.equalsIgnoreCase(T_ITEM) && currentMessage != null){
                            messages.add(currentMessage);
                        } else if (name.equalsIgnoreCase(T_CHANNEL)){
                            done = true;
                        }
                        break;
                }
                eventType = parser.next();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return messages;
    }
}

The parse method will go through all the XML document looking for the tags we need and the content will be stored as a Message object; at the end, a List with all the Messages found will be returned.

The advantage of the XmlPullParser class is that we don’t need to search for open/close or nested tags. the start_document, close_document, start_tag, end_tag are already defined as part of the class and we only need to worry about the tags we are looking for. Once any of them is found, its content will be stored in the Message object.

1.7) So far, all the contents of the RSS feed has been read and stored as Message objects. The next thing to do is to code the Adapter that will put this data into a ListView. I won’t go through the details for this class, but it should look like this:

image

Code:

package helpers;

import java.util.ArrayList;
import java.util.List;

import com.expertnotfound.rssfeed.R;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class FeedListAdapter extends BaseAdapter {
	private LayoutInflater li;

	//holds Message objects
	private List<Message> messages = new ArrayList<Message>();

	public FeedListAdapter(Context context, List<Message> items)
	{
		li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		if(items != null)
			messages = items;
	}

	public int getCount() {
		return messages.size();
	}

	public Object getItem(int position) {
		return messages.get(position);
	}

	public long getItemId(int position) {
		return position;
	}

	public View getView(int position, View convertView, ViewGroup parent) {

		View v = convertView;
		final Message message = messages.get(position);
		if (v == null) {
			v = li.inflate(R.layout.main, null);
		}

		final TextView mTitle = (TextView) v.findViewById(R.id.mLine1);
		mTitle.setText(message.getTitle());

		final TextView mAddress = (TextView) v.findViewById(R.id.mLine2);
		mAddress.setText(message.getDescription());

		return v;
	}
}

1.8) In order to test this code, go back to your main activity class and type the following:

public class RSSMain extends ListActivity {
	 public void onCreate(Bundle savedInstanceState) {
	  super.onCreate(savedInstanceState);
	  MyCustomFeedParser parser = new MyCustomFeedParser("https://expertnotfound.wordpress.com/feed/");
	  List<Message> messages = parser.parse();
	  setListAdapter(new FeedListAdapter(this, messages));
	 }
}

1.9) Before running your program, you must grant INTERNET permissions in the AndroidManifest.xml file:

image

1.10) Finally run your program and you should see something like this in the emulator:

image

Advertisements