Home > Tutorials > Creating a Simple Rss Application in Android (V2)

Creating a Simple Rss Application in Android (V2)

This is a refactored version of a previous rss application example which had 1 major issue and gathered some discutions around that tutorial.
Starting with Android 3.0 and higher when trying to perform a network operation from the UI thread, the NetworkOnMainThreadException is thrown. The previous example did not address this issue. Why? Well… the tutorial didn’t have the purpose to present a stable application from all points of view, its main intention was to show how to use the XmlPullParser to parse an XML file.
The new tutorial fixes that issue, and along with that brings some improvements that will make this example easily adaptable to your specific needs.

What is new in version 2:
a) The RSS content is downloaded and parsed in an IntentService, thus it does not block anymore the UI thread.

b) The application now uses Fragments, thus being able to handle properly configuration changes like screen orientation, while performing the background work.

c) The application uses a custom adapter instead of built in ArrayAdapter. This will allow us to work with custom objects, rather than using string arrays like in previous example.

d) The rss parser was modified and now the code is much simpler. With minor adjustments it can be easily adapted to parse a different rss feed.

Requirements didn’t change, we still need to parse the PCWorld’s rss feed (http://www.pcworld.com/index.rss) and display the headlines in a ListView. When clicking on a list item, the built in web browser opens and user is redirected to the corresponding article.

android rss reader

Lets begin first with modification of AndroidManifest file.
1. Add the internet permission:

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

2. As the application uses a Service, it should be specified in the AndroidManifest too:

<application ..>
   ...
   <service android:name=".RssService" />
</application>

The implementation of RssService will be shown later.

3. Add support library to project. Assuming you are using Eclipse: right click on project name -> Android Tools -> Add Support Library.

4. Here’s how MainActivity.java looks like:

public class MainActivity extends FragmentActivity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		if (savedInstanceState == null) {
			addRssFragment();
		}
	}

	private void addRssFragment() {
		FragmentManager manager = getSupportFragmentManager();
		FragmentTransaction transaction = manager.beginTransaction();
		RssFragment fragment = new RssFragment();
		transaction.add(R.id.fragment_container, fragment);
		transaction.commit();
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putBoolean("fragment_added", true);
	}
}

The MainActivity simply adds the RssFragment to the activity. First we check the savedInstanceState to see if it’s null, if so, it means we are entering the activity for the first time and the fragment can be added, otherwise we are returning from a configuration change, so we don’t need to add the fragment once again.

5. main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:id="@+id/fragment_container"
    android:layout_height="fill_parent" />

The main.xml contains a FrameLayout which will serve as the host for the fragment.

6. And here’s how RssFragment.java looks like:

public class RssFragment extends Fragment implements OnItemClickListener {

	private ProgressBar progressBar;
	private ListView listView;
	private View view;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setRetainInstance(true);
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		if (view == null) {
			view = inflater.inflate(R.layout.fragment_layout, container, false);
			progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
			listView = (ListView) view.findViewById(R.id.listView);
			listView.setOnItemClickListener(this);
			startService();
		} else {
			// If we are returning from a configuration change:
			// "view" is still attached to the previous view hierarchy
			// so we need to remove it and re-attach it to the current one
			ViewGroup parent = (ViewGroup) view.getParent();
			parent.removeView(view);
		}
		return view;
	}

	private void startService() {
		Intent intent = new Intent(getActivity(), RssService.class);
		intent.putExtra(RssService.RECEIVER, resultReceiver);
		getActivity().startService(intent);
	}

	/**
	 * Once the {@link RssService} finishes its task, the result is sent to this ResultReceiver.
	 */
	private final ResultReceiver resultReceiver = new ResultReceiver(new Handler()) {
		@SuppressWarnings("unchecked")
		@Override
		protected void onReceiveResult(int resultCode, Bundle resultData) {
			List<RssItem> items = (List<RssItem>) resultData.getSerializable(RssService.ITEMS);
			if (items != null) {
				RssAdapter adapter = new RssAdapter(getActivity(), items);
				listView.setAdapter(adapter);
			} else {
				Toast.makeText(getActivity(), "An error occured while downloading the rss feed.",
						Toast.LENGTH_LONG).show();
			}
			progressBar.setVisibility(View.GONE);
			listView.setVisibility(View.VISIBLE);
		};
	};

	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
		RssAdapter adapter = (RssAdapter) parent.getAdapter();
		RssItem item = (RssItem) adapter.getItem(position);
		Uri uri = Uri.parse(item.getLink());
		Intent intent = new Intent(Intent.ACTION_VIEW, uri);
		startActivity(intent);
	}
}

We are using the setRetainInstance() method to automatically save the fragment’s state across screen configuration changes. There’s however one thing that we should take in account. The onCreateView() method will be called each time on screen orientation. If the XML layout will be inflated again, you will loose the state of the views.
The solution is to keep the root of view hierarchy as a field in the fragment, so that is saved after configuration change. However, this view is still attached to the old hierarchy, so you need to remove it and re-attach it to the current hierarchy:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
	if (view == null) {
		view = inflater.inflate(R.layout.fragment_layout, container, false);
		//..... 
	} else {
		// If we are returning from a configuration change:
		// "view" is still attached to the previous view hierarchy
		// so we need to remove it and re-attach it to the current one
		ViewGroup parent = (ViewGroup) view.getParent();
		parent.removeView(view);
	}
	return view;
}

To get the result from the service, we are using the ResultReceiver. This class allows us to receive a callback result from the service once the task is finished. The only thing we need to do, is to override the onReceiveResult().
Notice how the resultReceiver is passed to the RssService, before starting it:

// ....
intent.putExtra(RssService.RECEIVER, resultReceiver);
getActivity().startService(intent);

Now the service will use the resultReceiver to notify the fragment that the service has finished its task and pass the data to it.


7. And this is the layout of the fragment: fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
      <ListView
        android:visibility="gone"
        android:id="@+id/listView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    </ListView>

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />

</RelativeLayout>

The layout contains the ListView which will hold the items, and the ProgressBar which is displayed while the rss feed is downloaded.

8. Every item from the list is represented by an object of type RssItem.

public class RssItem {

	private final String title;
	private final String link;

	public RssItem(String title, String link) {
		this.title = title;
		this.link = link;
	}

	public String getTitle() {
		return title;
	}

	public String getLink() {
		return link;
	}
}

Thus, the title and the link of an rss item is encapsulated in a single object, so we don’t have to store all the titles, and all the links in separate arrays like we did in previous version.

9. And here’s the RssAdapter that works with the rss items:

public class RssAdapter extends BaseAdapter {

	private final List<RssItem> items;
	private final Context context;

	public RssAdapter(Context context, List<RssItem> items) {
		this.items = items;
		this.context = context;
	}

	@Override
	public int getCount() {
		return items.size();
	}

	@Override
	public Object getItem(int position) {
		return items.get(position);
	}

	@Override
	public long getItemId(int id) {
		return id;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder;
		if (convertView == null) {
			convertView = View.inflate(context, R.layout.rss_item, null);
			holder = new ViewHolder();
			holder.itemTitle = (TextView) convertView.findViewById(R.id.itemTitle);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}
		holder.itemTitle.setText(items.get(position).getTitle());
		return convertView;
	}

	static class ViewHolder {
		TextView itemTitle;
	}
}

We extend BaseAdapter and provide implementations for the inherited methods.

10. rss_item.xml: the layout of an item from the list.

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/itemTitle"
    android:textSize="18dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

11. Next is the implementation of RssService.java

public class RssService extends IntentService {

	private static final String RSS_LINK = "http://www.pcworld.com/index.rss";
	public static final String ITEMS = "items";
	public static final String RECEIVER = "receiver";

	public RssService() {
		super("RssService");
	}

	@Override
	protected void onHandleIntent(Intent intent) {
		Log.d(Constants.TAG, "Service started");
		List<RssItem> rssItems = null;
		try {
			PcWorldRssParser parser = new PcWorldRssParser();
			rssItems = parser.parse(getInputStream(RSS_LINK));
		} catch (XmlPullParserException e) {
			Log.w(e.getMessage(), e);
		} catch (IOException e) {
			Log.w(e.getMessage(), e);
		}
		Bundle bundle = new Bundle();
		bundle.putSerializable(ITEMS, (Serializable) rssItems);
		ResultReceiver receiver = intent.getParcelableExtra(RECEIVER);
		receiver.send(0, bundle);
	}

	public InputStream getInputStream(String link) {
		try {
			URL url = new URL(link);
			return url.openConnection().getInputStream();
		} catch (IOException e) {
			Log.w(Constants.TAG, "Exception while retrieving the input stream", e);
			return null;
		}
	}
}

The service’s job is to parse the rss feed and send the list of items to the fragment.

12. The actual xml parser, PcWorldRssParser.java

public class PcWorldRssParser {

	// We don't use namespaces
	private final String ns = null;

	public List<RssItem> parse(InputStream inputStream) throws XmlPullParserException, IOException {
		try {
			XmlPullParser parser = Xml.newPullParser();
			parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
			parser.setInput(inputStream, null);
			parser.nextTag();
			return readFeed(parser);
		} finally {
			inputStream.close();
		}
	}

	private List<RssItem> readFeed(XmlPullParser parser) throws XmlPullParserException, IOException {
		parser.require(XmlPullParser.START_TAG, null, "rss");
		String title = null;
		String link = null;
		List<RssItem> items = new ArrayList<RssItem>();
		while (parser.next() != XmlPullParser.END_DOCUMENT) {
			if (parser.getEventType() != XmlPullParser.START_TAG) {
				continue;
			}
			String name = parser.getName();
			if (name.equals("title")) {
				title = readTitle(parser);
			} else if (name.equals("link")) {
				link = readLink(parser);
			}
			if (title != null && link != null) {
				RssItem item = new RssItem(title, link);
				items.add(item);
				title = null;
				link = null;
			}
		}
		return items;
	}

	private String readLink(XmlPullParser parser) throws XmlPullParserException, IOException {
		parser.require(XmlPullParser.START_TAG, ns, "link");
		String link = readText(parser);
		parser.require(XmlPullParser.END_TAG, ns, "link");
		return link;
	}

	private String readTitle(XmlPullParser parser) throws XmlPullParserException, IOException {
		parser.require(XmlPullParser.START_TAG, ns, "title");
		String title = readText(parser);
		parser.require(XmlPullParser.END_TAG, ns, "title");
		return title;
	}

	// For the tags title and link, extract their text values.
	private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {
		String result = "";
		if (parser.next() == XmlPullParser.TEXT) {
			result = parser.getText();
			parser.nextTag();
		}
		return result;
	}
}

The source code of the project is hosted on github. If you want to download the whole project follow this link: https://github.com/vgrec/SimpleRssReader

About these ads
  1. June 2, 2013 at 12:18 am | #1

    OK, I’m following you right now! Your blog looks great. I’m currently attempting to learn how to program for Android and it looks like your blog could help me. If you want some laughs have a look at my blog wannabecodemonkey.wordpress.com

  2. faisal
    June 8, 2013 at 3:23 pm | #2

    Excellent work. It has greatly helped me to understand the nitty gritty of android programming being a novice. Thanks

  3. faisal
    June 8, 2013 at 3:31 pm | #3

    Grec can you explain a bit about RssAdapter you have used in here? Actually I am a newbie and could not understand the concept of Adapters being used here for listview, especially I could not understand getView() method used in that class. Please can you put some light on it. Thanks.

    • June 8, 2013 at 8:33 pm | #4

      Adapters act as a bridge between a UI component and a collection of data. We have the Service that downloads and parses the rss feed, then it returns a list of rss items to the activity – this is the collection of data. And we have the ListView which should display that data – this is the UI component. The RssAdapter sits between them and connects the ListView with the data.

      The getView() is responsible for defining the layout of each item from that data source, and it’s called for every item in the data source.
      Watch this tutorial for a in depth explanation: http://www.youtube.com/watch?v=FP2gElnwTSs

  4. June 20, 2013 at 11:45 am | #5

    I can’t find main.xml in the Project folder within Eclipse. What have I done wrong? Thanks!

  5. Srihari
    July 31, 2013 at 9:09 pm | #6

    Hi,

    When I try the above given code, I get an error saying the variable TAG in the line Log.d(Constants.TAG, “Service started”) cannot be resolved. Does that mean I have got the wrong import? I changed TAG to _COUNT as suggested by Eclipse and the app is constantly in the fetch state from internet.
    Any idea to work around??

    • Srihari
      July 31, 2013 at 9:22 pm | #7

      I found out that the Constants.java is what I missed out. Got it done. Still the same fetch symbol in the app. Please help

      • Ottocesc
        October 19, 2013 at 6:26 pm | #8

        You did the same misstake as me by implementing all necessary packages with Ctrl + SHIFT + O. and it added a Constants package which overwrites the Constants class
        .

  6. Salim
    September 16, 2013 at 6:29 am | #9

    Thank you for the nice tutorial.

    How can we manually refresh the listView to fetch updated news please ?

    Regards,
    Salim

    • September 17, 2013 at 6:40 pm | #10

      You would make another http request and store the new results in the “items” array (that is passed to RssAdapter). Then call adapter.notifyDataSetChanged() to notify the adapter that data has been changed, which will refresh the ListView.

  7. xIvanJ
    October 2, 2013 at 10:08 am | #11

    Hey, I’ve made one myself. But when I open it in the application it only reads the first three sentences, without pictures or anything else. Any suggestions on how to fix this? Please reply !

    Ivan

  8. Shawn
    October 14, 2013 at 9:54 pm | #12

    Srihari :
    I get an error saying the variable TAG in the line Log.d(Constants.TAG, “Service started”) cannot be resolved. Does that mean I have got the wrong import? I changed TAG to _COUNT as suggested by Eclipse and the app is constantly in the fetch state from internet.
    Any idea to work around??

    I am having the same issue. Also what do you mean by

    Srihari :
    I found out that the Constants.java is what I missed out. Got it done. Still the same fetch symbol in the app.

  9. David f
    October 21, 2013 at 5:50 pm | #13

    Hi, i wanted to ask if you could point me in the right direction. I’m trying to implement a way to add a text file with a series of rss feeds in the src folder and have the appplication read the feeds from that file instead of having to code just one feed into the app

    • October 21, 2013 at 5:57 pm | #14

      The right place to store the text file is in the “assets”, or res/raw directory. Then read it from there, get the feed(s) and use like in the example.

  10. Ottocesc
    October 23, 2013 at 9:38 am | #15

    Hi,
    Such a great tutorial, thanks for this!
    I would like to pass the Rss title to a dialog message in an easy way based on your code.
    I tried to startService() from my DialogContentManager class:

    public class DialogContentManager {

    private Object foreignDialogContent;
    private boolean interactive=false;
    private Context context;
    private List items;

    public DialogContentManager(Context context){
    //context of my simple HelloWorld MainActivity where I will call dialogbulder.show()
    this.context=context;
    }
    public String getDialogContent(){
    startService();
    RssItem item = items.get(4);
    String content = item.getTitle();
    return content;
    }

    private void startService() {
    Intent intent = new Intent(context, RssService.class);
    intent.putExtra(RssService.RECEIVER, resultReceiver);
    context.startService(intent);
    }

    private final ResultReceiver resultReceiver = new ResultReceiver(new Handler()) {
    @SuppressWarnings(“unchecked”)
    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
    // progressBar.setVisibility(View.GONE);
    items = (List) resultData.getSerializable(RssService.ITEMS);

    if (items == null) {
    Toast.makeText(context, “An error occured while downloading the rss feed.”,
    Toast.LENGTH_LONG).show();
    }
    };
    };

    Problem with it that items are empty getDialogContent…
    How can I directly get the resulted items in the easiest way (of course I don’t need ListView and fragments…)?
    Thanks for any reply.

  11. Ottocesc
    November 20, 2013 at 3:15 pm | #16

    Any uodate on how could I only get the Rss title out of the xml and pass it simply to a dialog (based on your application)?

  12. chaitra
    December 27, 2013 at 5:22 am | #17

    i m getting blank page..pls help me..

    • February 1, 2014 at 9:19 am | #18

      I am also getting a blank page. I added check for internet connectivity. I am a noob by the way.

      • February 20, 2014 at 6:37 pm | #19

        also me get blank page with loading progress

  13. January 16, 2014 at 8:38 pm | #20

    How can I hide (no parse) the first row channel title tag? Thank’s and great job!

    • January 27, 2014 at 7:34 pm | #21

      You could take a look at the previous version of tutorial: http://androidresearch.wordpress.com/2012/01/21/creating-a-simple-rss-application-in-android/ to see how it’s done (scroll down to section number 5).
      The idea is that we track whether we are reading the title of the channel tag, or of the item tag.

      However, there’s an easier work around. Instead of dealing with parsing you just could return a sub list of the original list without the first element, which will mean without the title of channel tag.

      private List<RssItem> readFeed(XmlPullParser parser) throws XmlPullParserException, IOException {
      		//...		
              List<RssItem> items = new ArrayList<RssItem>();
              //...
              return items.subList(1, items.size());
          }
      
  14. January 20, 2014 at 12:28 pm | #22

    A very well explained and well structured example. Unfortunately, and FYI, I simply get “Unfortunately, RSS project has stopped” when trying to run it on the debugger from IntelliJ based AndroidStudio on a Nexus 4 with 4.4.2. I’m not familiar with crash tracing on Android to tell you much else.

    • January 27, 2014 at 7:20 pm | #23

      Well, unfortunately without a stacktrace it’s impossible to know why the app crashed.

      For the reason of the crash you should look in the LogCat (if I’m not mistaken in Intellij Idea this is when you click on “Android”, at the very bottom in the IDE, near the status bar). The LogCat provides detailed information about the cause of the crash.

  15. Eno
    January 30, 2014 at 9:25 am | #24

    Nice One man

  16. Nico
    February 3, 2014 at 2:28 am | #25

    Nice tutorial! I’ve followed it and managed to make the application work, but there is one problem: I only get a blank screen without information.
    It there anything that has changed?

  17. nrjana
    March 2, 2014 at 1:13 pm | #26

    very very nice and helpful tutorial…thank you very much. but there is one issue…the app crashes when there is no internet connection.How to handle that?pls respond

  18. nrjana
    March 8, 2014 at 11:21 am | #28

    Sir thanks again, but can you please tell how to parse images from xml and show them in a listview?

    • March 10, 2014 at 9:22 am | #29

      You will have to extract the image link from the XML. Once you have the link, you download the image like you would download a file from the internet.

  19. AB
    March 12, 2014 at 5:40 pm | #30

    The tutorial given above missed creating a class called Constants. Without creating it you get an error. You can find the program for Constants.java in the Github link provided above.

  20. March 25, 2014 at 7:03 am | #31

    Why you did not use an AsyncTask?

  21. March 25, 2014 at 10:35 am | #32

    Yes, I could have use an AsyncTask too. The issue with AsyncTasks is that they are coupled with the Activity life cycle. This means you’ll have to handle yourself configuration changes, such as screen orientation, while the task is being executed.

  1. June 1, 2013 at 7:17 pm | #1

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 484 other followers

%d bloggers like this: