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

66 thoughts on “Creating a Simple Rss Application in Android (V2)

  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. Excellent work. It has greatly helped me to understand the nitty gritty of android programming being a novice. Thanks

  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.

    1. 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. 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??

    1. 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

      1. 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
        .

    1. 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.

  5. 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

  6. 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.

  7. 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

  8. 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.

  9. 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)?

    1. You could take a look at the previous version of tutorial: https://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());
          }
      
      1. When i add this line: return items.subList(1, items.size())

        its crash at the RssService Line 40

  10. 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.

    1. 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.

  11. 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?

  12. 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

  13. 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.

  14. 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.

  15. Really good guide 🙂

    but how updated the view when the source of the RSS updated ?
    From what I’ve seen the user has to close reopen the app, it is possible to do it immediately ?

    Thanks.

  16. Hello, great tutorial.
    I have a question. If I have 2 fragments with 2 different feed list, what can I do? I created a new fragment and a new RssService class with different parameters. The other classes I leave unchanged. But work only the first fragment. The second fragment show only the loader widget but don’t load the feed list. Can you resolve my problem? Thanks.

  17. Hi, i’m new to Android and Java. Got this working but i’m having a hard time customizing it.
    How can i make it so it displays the title and description in the list and don’t link to the website?

  18. Hi, i am getting error message from line 17 of mainavtivity.java from your tutorial above saying
    /////Error:(24, 20) error: no suitable method found for add(int,RssFragment)
    method FragmentTransaction.add(int,Fragment,String) is not applicable/////
    Error line contains:17 transaction.add(R.id.fragment_container, fragment); )

    below you can see what i did.
    i tried to using Mainavtivity extends Fragment, by importing
    import android.os.Bundle;
    import android.support.v4.app.FragmentActivity;
    import android.support.v4.app.FragmentManager;
    import android.support.v4.app.FragmentTransaction;

    public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_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);
    }
    }
    ******************************************************

  19. i tried this code,and it is working also well..but i had a problem,when i rotate my phone,the application crashes.will you please tell me how can i avoid this problem..

  20. I got problem that the progressbar was just rolling but nothing else happened. There wasn’t any wrong code but it didn’t just showed me those RSS Feeds, like the service wasn’t existing. I managed to solve the problem. I added this line to AndroidManifest.xml inside :

  21. Ah this commentbox doesn’t like all characters. So, inside the application in manifest. I added service to manifest to let it exist. Another try:

    service android:enabled=”true” android:name=”.RssService” /

  22. Hello, Im really hoping someone might still be following this

    Ive implemented everything accordingly, and i dont get any build crashes, but when i load the RSS Activity, i only get the loading screen. No content ever shows up.

    the only crash i just found, is (accidentally) by tilting to landscape view whilst the RSS feed activity is running.

    anyway, even if i leave in same orientation, the feed never loads. just blank screen, with loading circle image rotating.
    Please advise.

  23. Very cool tutorial and everything works as it should until I rotate my device and it crashes with these errors: “Unable to start activity ComponentInfo” and “Caused by: java.lang.NullPointerException: Attempt to invoke virtual method ‘void android.view.ViewGroup.removeView(android.view.View)’ on a null object reference”.

  24. Everything goes fine but there it shows error in XML

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

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