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.
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
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
Excellent work. It has greatly helped me to understand the nitty gritty of android programming being a novice. Thanks
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.
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
I can’t find main.xml in the Project folder within Eclipse. What have I done wrong? Thanks!
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??
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
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
.
Dude what should i write in constant.java ?????
Thank you for the nice tutorial.
How can we manually refresh the listView to fetch updated news please ?
Regards,
Salim
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.
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
I am having the same issue. Also what do you mean by
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
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.
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.
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)?
i m getting blank page..pls help me..
I am also getting a blank page. I added check for internet connectivity. I am a noob by the way.
also me get blank page with loading progress
Add this line to your manifest under the line:
Add this line under in your Manifest file:
i’m getting a blank page….plz give me some suggestion to overcome that
How can I hide (no parse) the first row channel title tag? Thank’s and great job!
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.
When i add this line: return items.subList(1, items.size())
its crash at the RssService Line 40
items.remove(0); just before return.items
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.
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.
Nice One man
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?
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
Check the presence of internet connection before downloading the rss feed.
https://androidresearch.wordpress.com/2012/01/06/android-detecting-internet-connection/
Sir thanks again, but can you please tell how to parse images from xml and show them in a listview?
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.
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.
Why you did not use an AsyncTask?
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.
Thanks!!!
Hi, can you tell me how can I make a widget which shows the Rss feed on it. I need it very urgently.
Very nice tutorial, but how to load differnent RSS Links ?
Is it works for 4.4.2 + version?
You have to give the details of android version in the manifest file. So it should work.
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.
I would like change Rss language from Facebook section: en-us to other language, any solutions ?
how to get a only the new feed as notification . Like the times of India app
I wonder why you are using a Service. AsyncTask or a subclass of that would have been better (i.e lightweight), right?
Reblogged this on and commented:
Now! You can easily make RSS android app
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.
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?
Good Tutorial but how I can do that on an android wear (smartwatch) ?? Can you explain me please ?
Reblogged this on Blog de Fabrice HAUHOUOT and commented:
Développez un lecteur de flux RSS sur Android !
i really need this thank you
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);
}
}
******************************************************
Make sure your RssFragment extends the Fragment class from “android.support.v4.app.Fragment”, and not “android.app.Fragment”. Check your imports in RssFragment.
Voilà !
The App just came to life. Superb !
Thanks mate!
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..
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 :
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” /
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.
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”.
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();
}
}
There’s XML error please help
What could I do o get the Content of each post as well and list it with the Titles!?