Saturday, May 8, 2010

A simple RSS reader II, implement with RSSFeed & RSSItem

In the previous article "A simple RSS reader, in ListView", I just show a very simple RSS reader, with a very limited handling on the RSSHandle events. Here, I will show in more details, also in better orgination.

A simple RSS reader II, implement with RSSFeed & RSSItem

First of all, the source of the feed is changed to http://www.gov.hk/en/about/rss/govhkrss.data.xml, it's a feed from Hong Kong Government; because the original blogger's feed is too complicated for me to demo here!

Here, RSSHandler, RSSFeed and RSSItems are implemented as separated class for better organization. The application have a single object of RSSFeed class, which consist a list of RSSItem object. More details (Title, PubDate, Description and Link) of the feed and individual items are saved in the structure, which can be retrieved later.

AndroidManifest.xml to grant "android.permission.INTERNET" to the application. (Refer to last article "A simple RSS reader, using Android's org.xml.sax package")

Keep the rsslist.xml as before
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rowtext"
android:layout_width="fill_parent"
android:layout_height="25px"
android:textSize="10sp" />


Modify main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/feedtitle" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/feeddescribtion" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/feedpubdate" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:id="@+id/feedlink" />
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="No Data" />
</LinearLayout>


Implement /src/com.exercise.AndroidRssReader/RSSItem.java
package com.exercise.AndroidRssReader;

public class RSSItem {

private String title = null;
private String description = null;
private String link = null;
private String pubdate = null;

RSSItem(){
}

void setTitle(String value)
{
title = value;
}
void setDescription(String value)
{
description = value;
}
void setLink(String value)
{
link = value;
}
void setPubdate(String value)
{
pubdate = value;
}

String getTitle()
{
return title;
}
String getDescription()
{
return description;
}
String getLink()
{
return link;
}
String getPubdate()
{
return pubdate;
}

@Override
public String toString() {
// TODO Auto-generated method stub
return title;
}
}


Implement /src/com.exercise.AndroidRssReader/RSSFeed.java
package com.exercise.AndroidRssReader;

import java.util.List;
import java.util.Vector;

public class RSSFeed {
private String title = null;
private String description = null;
private String link = null;
private String pubdate = null;
private List<RSSItem> itemList;

RSSFeed(){
itemList = new Vector<RSSItem>(0);
}

void addItem(RSSItem item){
itemList.add(item);
}

RSSItem getItem(int location){
return itemList.get(location);
}

List<RSSItem> getList(){
return itemList;
}

void setTitle(String value)
{
title = value;
}
void setDescription(String value)
{
description = value;
}
void setLink(String value)
{
link = value;
}
void setPubdate(String value)
{
pubdate = value;
}

String getTitle()
{
return title;
}
String getDescription()
{
return description;
}
String getLink()
{
return link;
}
String getPubdate()
{
return pubdate;
}

}


Implemenet /src/com.exercise.AndroidRssReader/RSSHandler.java
package com.exercise.AndroidRssReader;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class RSSHandler extends DefaultHandler {

final int state_unknown = 0;
final int state_title = 1;
final int state_description = 2;
final int state_link = 3;
final int state_pubdate = 4;
int currentState = state_unknown;

RSSFeed feed;
RSSItem item;

boolean itemFound = false;

RSSHandler(){
}

RSSFeed getFeed(){
return feed;
}

@Override
public void startDocument() throws SAXException {
// TODO Auto-generated method stub
feed = new RSSFeed();
item = new RSSItem();

}

@Override
public void endDocument() throws SAXException {
// TODO Auto-generated method stub
}

@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// TODO Auto-generated method stub

if (localName.equalsIgnoreCase("item")){
itemFound = true;
item = new RSSItem();
currentState = state_unknown;
}
else if (localName.equalsIgnoreCase("title")){
currentState = state_title;
}
else if (localName.equalsIgnoreCase("description")){
currentState = state_description;
}
else if (localName.equalsIgnoreCase("link")){
currentState = state_link;
}
else if (localName.equalsIgnoreCase("pubdate")){
currentState = state_pubdate;
}
else{
currentState = state_unknown;
}

}

@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
// TODO Auto-generated method stub
if (localName.equalsIgnoreCase("item")){
feed.addItem(item);
}
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
// TODO Auto-generated method stub

String strCharacters = new String(ch,start,length);

if (itemFound==true){
// "item" tag found, it's item's parameter
switch(currentState){
case state_title:
item.setTitle(strCharacters);
break;
case state_description:
item.setDescription(strCharacters);
break;
case state_link:
item.setLink(strCharacters);
break;
case state_pubdate:
item.setPubdate(strCharacters);
break;
default:
break;
}
}
else{
// not "item" tag found, it's feed's parameter
switch(currentState){
case state_title:
feed.setTitle(strCharacters);
break;
case state_description:
feed.setDescription(strCharacters);
break;
case state_link:
feed.setLink(strCharacters);
break;
case state_pubdate:
feed.setPubdate(strCharacters);
break;
default:
break;
}
}

currentState = state_unknown;
}


}


Modify /src/com.exercise.AndroidRssReader/AndroidRssReader.java
package com.exercise.AndroidRssReader;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class AndroidRssReader extends ListActivity {

private RSSFeed myRssFeed = null;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

try {
URL rssUrl = new URL("http://www.gov.hk/en/about/rss/govhkrss.data.xml");
SAXParserFactory mySAXParserFactory = SAXParserFactory.newInstance();
SAXParser mySAXParser = mySAXParserFactory.newSAXParser();
XMLReader myXMLReader = mySAXParser.getXMLReader();
RSSHandler myRSSHandler = new RSSHandler();
myXMLReader.setContentHandler(myRSSHandler);
InputSource myInputSource = new InputSource(rssUrl.openStream());
myXMLReader.parse(myInputSource);

myRssFeed = myRSSHandler.getFeed();

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

if (myRssFeed!=null)
{
TextView feedTitle = (TextView)findViewById(R.id.feedtitle);
TextView feedDescribtion = (TextView)findViewById(R.id.feeddescribtion);
TextView feedPubdate = (TextView)findViewById(R.id.feedpubdate);
TextView feedLink = (TextView)findViewById(R.id.feedlink);
feedTitle.setText(myRssFeed.getTitle());
feedDescribtion.setText(myRssFeed.getDescription());
feedPubdate.setText(myRssFeed.getPubdate());
feedLink.setText(myRssFeed.getLink());

ArrayAdapter<RSSItem> adapter =
new ArrayAdapter<RSSItem>(this,
android.R.layout.simple_list_item_1,myRssFeed.getList());
setListAdapter(adapter);
}
}
}


Download the files.

Next: A simple RSS reader III, show details once item clicked.

22 comments:

thatvirtualboy said...

When calling this for a button click from my existing application, which java file do I call?

Also, I already have a "main.xml" so what should I call the one that comes with this app?

Thanks!

Erik said...

ryan_k: Do you means how to start another activity within one activity? pls refer to http://android-er.blogspot.com/2009/08/exercise-intent-bundle.html

thatvirtualboy said...

Thanks for the reply! This is how I'm calling it:

public void onClick(View src) {
switch(src.getId()){

case R.id.buttonMessageArchive:
i = new Intent(this, AndroidRssReader.class);
startActivity(i);
break;

When I do this, I get a 'force close' every time I click the button. Any ideas?

Thanks again!

thatvirtualboy said...

I got it working, but my items are 'un-clickable.'

The items I am pulling are audio feeds. How do I get them to play/download when clicked?

Thanks for your help!

Holly Cairns said...

Hi Android-er,

This has "Modify main.java" but did you mean to modify main.xml?

And, for the RSSItem.java I'm running into the error that RSSItem is already defined.

(and for that RSSItem.java is there an import missing? THANKS!)

Erik said...

ryan_k: I have no idea on audio feeds right now. Sorry!

Erik said...

Holly Cairns: Yes, it's main.xml, Thx.

"RSSItem is already defined" I can't catch! Do you double define something?

"import missing"? Suppose nothing missed, if you work on Eclipse, if import missed, Eclipse will remind you.

Paolinho said...

hi.. very good article..
sorry but i have a question..
if i want to see the title and the date into a listview???the title up and the date down in the row of listview??

Erik said...

Paolinho: pls refer to Apply custom adapter to ListView for RSS Reader

Lorenz Bischof said...

When I click the button it force closes with this output in logcat:


D/AndroidRuntime( 8578): Shutting down VM
W/dalvikvm( 8578): threadid=3: thread exiting with uncaught exception (group=0x4001e3c0)
E/AndroidRuntime( 8578): Uncaught handler: thread main exiting due to uncaught exception
E/AndroidRuntime( 8578): java.lang.NullPointerException
E/AndroidRuntime( 8578): at android.widget.TabHost.dispatchWindowFocusChanged(TabHost.java:295)
E/AndroidRuntime( 8578): at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:673)
E/AndroidRuntime( 8578): at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:673)
E/AndroidRuntime( 8578): at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:673)
E/AndroidRuntime( 8578): at android.view.ViewRoot.handleMessage(ViewRoot.java:1841)
E/AndroidRuntime( 8578): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime( 8578): at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime( 8578): at android.app.ActivityThread.main(ActivityThread.java:4595)
E/AndroidRuntime( 8578): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 8578): at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime( 8578): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
E/AndroidRuntime( 8578): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
E/AndroidRuntime( 8578): at dalvik.system.NativeStart.main(Native Method)
I/Process ( 734): Sending signal. PID: 8578 SIG: 3
I/dalvikvm( 8578): threadid=7: reacting to signal 3
I/dalvikvm( 8578): Wrote stack trace to '/data/anr/traces.txt'

Lorenz Bischof said...

well I just made a new project and it works... somehow something with my main app is calling the rss part wrong...

HELP!!

amber said...

Hello, this is a great tutorial but i can't get mine to work, when i run it it says the application has stopped unexpectedly. Could you tell me why this is? thanks!

Erik said...

hello amber,

did u set "android.permission.INTERNET" in AndroidManifest.xml?

amber said...

yes i did, i was able to get your first example to work but when you started working with listviews is when i started having problems. not sure why. any ideas? thanks

amber said...

i ran my debugger and it says the content needs to have a list view where the id is R.id.list which is the id you gave the listview in the main.xml, is there somewhere your implementing this that is not shown in your post. im unable to download the source code

Erik said...

hello amber,

I download the code and run again, it should work ok.

Would you give me your email? I can send you the code.

amber said...

amber_king@ymail.com

Thanks so much

amber said...

Okay i figured out what it was... i had my list view id at @+id/list instead of @android:id/list...

Ben said...

Is there a way to make it only show 10 articles at a time?

Anonymous said...

sir,

i am new to RSS and downloaded a codes from "A simple RSS reader II, implement with RSSFeed & RSSItem" actally the codes works well but when i replaced the URL with
http://feeds.feedburner.com/bbs/NHCG?format=xml , it displays --No Data--
pls help

Andi said...

Hi, thanks for your great tutorials! I added the content of the rss fedd to the detail view and got the problem, that it sometimes was cropped.

Now I figured out, that the characters method could be passed several times within the same xml element. Your example works just fine, but if someone like me adds the content of the feed they should use a StringBuilder, append the characters in the characters method an move the state reset (currentState = state_unknown;) from characters method to endElement method.

Maybe it helps someone.

Anonymous said...

i got the code but i want to show images also so pls help me