Network Resources - RSS

Intro

One of the more ubiquitous formats for delivery of internet-based data is a special standardization of XML known as RSS.  RSS is used by many websites as a way to provide a feed for their sites news, blogs, media or other site content.  Because RSS is standardized, it allows us to write applications that can pull data from multiple online sources without having to write site-specific XML parsing rules.  

Setting the Data Source

There are several ways to retrieve content from the internet.  For the sake of time, this lesson will focus on just one.  In normal Java applications, you would need to import 3rd-party libraries.  Luckily for us, the Android sdk comes with built-in libraries for our use!  Since the source for this lesson is XML, we'll use the recommended XMLPullParser class provided by the included org.xmlpull.v1 library.  

The first thing you need to do is instantiate a new XMLPullParser:

XmlPullParser parser = Xml.newPullParser();

Once the XMLPullParser has been instantiated, we give it an input in the form of a URL object.  This URL is created by passing a properly formatted string (like you would type in a web browser's address bar) to the URL constructor:

// website URL String
String urlString = "http://www.newssource.com/rss";

// create URL object from String
URL feedURL = new URL(urlString);

Now that we have an XMLPullParser and a URL, we marry the two objects by setting the parser's input to a stream created from the URL:

​// create InputStream from URL
InputStream inputStream = feedURL.openStream();

// set XMLPullParser to use the input stream
parser.setInput(inputStream, null);

The RSS Format

Let's review the RSS format.  Examine the following RSS news feed: http://www.npr.org/rss/rss.php?id=1001.  As previously discussed, the RSS format contains a channel element, with item sub-elements.  Each item element has the following sub-elements:

There are other possible elements, but we'll focus on these four for this example.  We use the parser that we created above to read each element name and act accordingly.  In true Object-Oriented fashion, for this lesson we'll define a Class to handle the data for each item element.  As our parser parses items from the given RSS feed, a new RssItem item is created.  Also, notice the toString() method.  While not required, it's planning ahead.  We'll need it if we want to display our RSSItem properly in a simple ListView.

public class RssItem {
	
	private String link;
	private String pubDate;
	private String description;
	private String title;
	
	public String getLink() {
		return link;
	}
	public void setLink(String link) {
		this.link = link;
	}
	public String getPubDate() {
		return pubDate;
	}
	public void setPubDate(String pubDate) {
		this.pubDate = pubDate;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}

        @Override
        public String toString() {
                return this.title;
        }
}

Retrieving RSS Feeds

At this point, the parser is ready to go!  In order to retrieve the items from the feed, we use the parser to loop through the input feed.  Have a look at the code we'll use to do this, and we'll go over it after:

int eventType = parser.getEventType();
			
boolean done = false;

RssItem currentRSSItem= new RssItem();

while (eventType != XmlPullParser.END_DOCUMENT && !done) {
	String name = null;
	switch (eventType) {
	case XmlPullParser.START_TAG:
		name = parser.getName();
		if (name.equalsIgnoreCase("item")) {
			// a new item element
			currentRSSItem = new RssItem();
		} else if (currentRSSItem != null) {
			if (name.equalsIgnoreCase("link")) {
				currentRSSItem.setLink(parser.nextText());
			} else if (name.equalsIgnoreCase("description")) {
				currentRSSItem.setDescription(parser.nextText());
			} else if (name.equalsIgnoreCase("pubDate")) {
				currentRSSItem.setPubDate(parser.nextText());
			} else if (name.equalsIgnoreCase("title")) {
				currentRSSItem.setTitle(parser.nextText());
			}
		}
		break;
	case XmlPullParser.END_TAG:
		name = parser.getName();
		if (name.equalsIgnoreCase("item") && currentRSSItem != null) {
			rssItemList.add(currentRSSItem);
		} else if (name.equalsIgnoreCase("channel")) {
			done = true;
		}
		break;
	}
	eventType = parser.next();
}

When the above code is ran, a new RSSItem object is instantiated.  We then use the XMLPullParser to populate the fields of the RSSItem.

Displaying the Items

Let's put it all together by building an app that shows a list of titles from an RSS news feed.  In Android Studio, create a new Project, leaving everything as the default calue.  

In the activity_main.xml layout file, add a ListView.  This will be used to display the titles that we fetch:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/newsListView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

In MainActivity.java:

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.util.Xml;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends Activity {

	public ListView newsListView;
	public ArrayAdapter<RssItem> adapter;
	public List<RssItem> rssItemList = new ArrayList<RssItem>();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		newsListView = (ListView) findViewById(R.id.newsListView);
		adapter = new ArrayAdapter<RssItem>(this, 
                        android.R.layout.simple_list_item_1, 
                        rssItemList);
		newsListView.setAdapter(adapter);

		String siteURL = "http://www.npr.org/rss/rss.php?id=1001";
		new RetrieveFeedTask().execute(siteURL);
	}

	public List<RssItem> parseRSS(URL feedURL)
			throws XmlPullParserException, IOException {
				
		XmlPullParser parser = Xml.newPullParser();
		parser.setInput(feedURL.openStream(), null);
		
		int eventType = parser.getEventType();
				
		boolean done = false;
		
		RssItem currentRSSItem= new RssItem();
		
		while (eventType != XmlPullParser.END_DOCUMENT && !done) {
		    String name = null;
		    switch (eventType) {
		    case XmlPullParser.START_TAG:
		    name = parser.getName();
		    if (name.equalsIgnoreCase("item")) {
			// a new item element
			currentRSSItem = new RssItem();
		    } else if (currentRSSItem != null) {
			if (name.equalsIgnoreCase("link")) {
			    currentRSSItem.setLink(parser.nextText());
			} else if (name.equalsIgnoreCase("description")) {
			    currentRSSItem.setDescription(parser.nextText());
			} else if (name.equalsIgnoreCase("pubDate")) {
			    currentRSSItem.setPubDate(parser.nextText());
			} else if (name.equalsIgnoreCase("title")) {
			    currentRSSItem.setTitle(parser.nextText());
			}
		    }
		    break;
		    case XmlPullParser.END_TAG:
			name = parser.getName();
			if (name.equalsIgnoreCase("item") && currentRSSItem != null) {
			    rssItemList.add(currentRSSItem);
			} else if (name.equalsIgnoreCase("channel")) {
			    done = true;
			}
			break;
		    }
		    eventType = parser.next();
		}
		return rssItemList;
	}
	
        //------- AsyncTask ------------//
	class RetrieveFeedTask extends AsyncTask<String, Integer, Integer> {

	    protected Integer doInBackground(String... urls) {
	    	try {
			URL feedURL = new URL(urls[0]);
			rssItemList = parseRSS(feedURL);

		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (XmlPullParserException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    	return 0;
	    }

	    protected void onPostExecute(Integer result) {
	        adapter.notifyDataSetChanged();
	    }
	}

}

Finally, you need to ensure you've set the proper permissions in the app's manifest file:

<uses-permission android:name="android.permission.INTERNET"/>

Go ahead and run it.  If all went well, you should be greeted by something like this: 

Using a Custom ArrayAdapter

If we want to show more than just the title in our ListView, we'll need to use a custom ArrayAdapter.  To do this, we'll subclass the ArrayAdapter class and override the getView() method to return our own view containing the contents we want to display.

First we'll need to create a layout to be used by each of the ListView elements (we'll call this example item_layout.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="5dp"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/titleText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="13sp"
        android:textColor="#cc0000" />

    <TextView
        android:id="@+id/descriptionText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:textSize="12sp" />

</LinearLayout>

Then, we subclass the ArrayAdapter.  This will allow us to use our new layout file for the list items:

import java.util.List;

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

public class RssItemAdapter extends ArrayAdapter<RssItem> {

	private Context context;

	public RssItemAdapter(Context context, int textViewResourceId,
			List<RssItem> items) {
		super(context, textViewResourceId, items);
		this.context = context;
	}

	public View getView(int position, View convertView, ViewGroup parent) {
		View view = convertView;
		if (view == null) {
		    LayoutInflater inflater = (LayoutInflater) context
			.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		    view = inflater.inflate(R.layout.item_layout, null);
		}

		RssItem item = getItem(position);
		if (item != null) {
		    // our layout has two TextView elements
		    TextView titleView = (TextView) view.findViewById(R.id.titleText);
		    TextView descView = (TextView) view
			.findViewById(R.id.descriptionText);

    		    titleView.setText(item.getTitle());
		    descView.setText(item.getDescription());
		}

		return view;
	}
}

Lastly, there's just one line to modify in MainActivity.java:

adapter = new RssItemAdapter(this, android.R.layout.simple_list_item_1, rssItemList);

Here's the result:

Task: Modify the RssItemAdapter class to display a Toast message showing the item date when a ListView item is clicked