Android Simple RSS Reader
RSS is a family of web feed formats used to publish frequently updated works—such as blog entries, news headlines, audio, and video—in a standardized format. – wikipedia
Basically RSS are xml files. We can read RSS files using applications called RSS Readers. In this tutorial we will create a ‘Simple RSS Reader’ app with some basic functionalities like:
1. A splash screen with a spinner and background image shown while the data XML being parsed.
2. From the feed returned by the Parser displaying the thumbnail image, title & date in a custom List View.
2. On click of the list item, the description shown in a separate Activity.
Here’s the final demo of the ‘Simple RSS Reader’ App on a emulator:
Analyzing the XML:
In this tutorial is used an external ‘xml’ (RSS) of the website [http://www.androidcentral.com]
RSS Link: http://www.mobilenations.com/rss/mb.xml
You can see the structure of the XML(tags) used in it by going to the code view of the link. Here is the example ‘item’ <tag> used in the XML:
<item> <title>Voice Controlled: A guide to using speech with Windows Phone 8</title> <description> <div id="comment-wrapper-nid-13725" ></div> <div><div><div> <p style="" > <img alt="Getting chatty with Windows Phone 8" height="365" src="http://cdn.wpcentral.com/sites/wpcentral.com/files/styles/large/public/field/image/2012/11/HandHolding_Speech_ws.jpg" width="650" /> </p> <p Speech has always been a part of Windows Phone and it has offered some nice ways of interacting with your handset by using your voice alone. </p> <p> We have put together a guide that should suit those that are new to Windows Phone and also to those that have upgraded to <a href="http://www.wpcentral.com/windows-phone-8" > Windows Phone 8 </a>. Interacting with voice recognition and commands can be a powerful and time saving way to use your phone efficiently and even safely. </p> <p> </p> </div></div></div> <div><div>Tag: </div><div><div> <a href="/tag/windowsphone8" > windowsphone8 </a> </div> <div> <a href="/tags/speech" >speech</a> </div> <div> <a href="/tags/how" >how-to</a> </div> <div> <a href="/tags/text-speech" >Text to Speech</a> </div> <div> <a href="/tags/text-voice" >text to voice</a> </div> <div> <a href="/tags/wp8" >WP8</a> </div> <div> <a href="/tags/wp7" >wp7</a> </div> </div> </div> <div><div><div> <a href="/category/how" >How To</a> </div></div></div> </description> <link>http://www.wpcentral.com/voice-controlled-guide-using-speech-windows-phone-8</link> <pubDate>Thu, 22 Nov 2012 14:12:52 +0000</pubDate> <guid isPermaLink="<a>false</a>">13725 at http://www.wpcentral.com</guid> <dc:creator>robert brand</dc:creator> </item>
Here from the above <item> we need to get some of the elements for our app, like we need a title(<title></title>) description(<description></description>) publication date(<pubDate></pubDate>) and finally a thumbnail image.
But as you can see in the item we dint find a image tag or any tag related to the image, but if you observe the description we have a image link inside it with in HTML tags. So to get he image link we need to parse the HTML description for ‘<img src=” image link”>’ .
So here we have to perform html parsing over the description which we will get after parsing the XML. So we need an HTML Parser, we use an external library called ‘jsoup’ to perform this html parsing. The library can be downloaded at: http://jsoup.org/download. Add the library to the project.
Also i used some external classes from: https://github.com/thest1/LazyList to Lazily load the images in the ListView. Here are the list of classes i used :
- FileCache.java
- ImageLoader.java
- MemoryCache.java
- Utils.java
Now lets get started with building process step by step:
step 1: Create a New Project (‘Simple RSS Reader’) with some package name & a default blank Activity.
Creating a Splash Screen:
step 2 : As you seen in the video demo a splash screen is shown on launch of the app where all the parsing of the data from the XML is done. So first lets create a Splash Screen Activity: SplashActivity.java(You can rename the blank activity created).
Next we need to create a layout for the Activity. Use a Relative layout to show an image and a loading spinner with some text below it. The final Layout file will look like this:
splash.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:contentDescription="@string/app_name" android:src="@drawable/mwm_small" /> <LinearLayout android:id="@+id/linearLayout1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/imageView" android:layout_centerHorizontal="true" android:layout_marginTop="20dp" android:orientation="horizontal" > <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginBottom="5dp" android:layout_marginLeft="10dp" android:text="@string/loading" /> </LinearLayout> </RelativeLayout>
In the SplashActivity.java set the content view to the layout created: setContentView(R.layout.splash);
step 3: Now before starting the parsing process we need to check for the Internet connectivity. If we are connected then we can proceed with the parsing else we will show an alert to check for connectivity. The following code shows how to check for connectivity and displaying a Dialog if there’s no connectivity:
ConnectivityManager conMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (conMgr.getActiveNetworkInfo() == null && !conMgr.getActiveNetworkInfo().isConnected() && !conMgr.getActiveNetworkInfo().isAvailable()) { // No connectivity - Show alert AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage( "Unable to reach server, \nPlease check your connectivity.") .setTitle("TD RSS Reader") .setCancelable(false) .setPositiveButton("Exit", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { finish(); } }); AlertDialog alert = builder.create(); alert.show(); } else { // Connected - Start parsing new AsyncLoadXMLFeed().execute(); }
Parsing the XML:
step 4: We need to perform the parsing in a separate thread, we achieve that by using the ‘AsyncTask’ Class. The reason to perform the parsing in a separate thread is if you start the parsing in the main thread or the UI thread then the application may clog as the Parsing is a potentially a time taking process.
So we create a sub class called ‘AsyncLoadXMLFeed’ which extends AsyncTack. In the class we implement the methods: doInBackground() and onPostExecute(). In doInBackground() method we start the Parsing process and once if the Parsing has done then it triggers the onPostExecute() method where we can perform action that has to be done after Parsing(starting the List Activity).
DOM Parser:
step 5: Before jumping in to the parsing we create two classes called: ‘RSSItem’ and ‘RSSFeed’ both implements Serializable.
RSSItem: This class contains the String variables which holds the required data from each item in the XML. We have also some setters and getters to set and get the item data. Here the final RSSItem class:
‘RSSItem.java‘:
public class RSSItem implements Serializable { private static final long serialVersionUID = 1L; private String _title = null; private String _description = null; private String _date = null; private String _image = null; void setTitle(String title) { _title = title; } void setDescription(String description) { _description = description; } void setDate(String pubdate) { _date = pubdate; } void setImage(String image) { _image = image; } public String getTitle() { return _title; } public String getDescription() { return _description; } public String getDate() { return _date; } public String getImage() { return _image; } }
RSSFeed: This class object holds all the Items (RSSItem’s) that have been parsed. It contains a List where we add the RSSItems by a method called addItem(RSSItem item):
List<RSSItem> _itemlist; void addItem(RSSItem item) { _itemlist.add(item); _itemcount++; }
We have also a method called getItemCount() to get the total number of items in the feed.
The final RSSFeed class is here:
‘RSSFeed.java‘
public class RSSFeed implements Serializable { private static final long serialVersionUID = 1L; private int _itemcount = 0; private List _itemlist; RSSFeed() { _itemlist = new Vector(0); } void addItem(RSSItem item) { _itemlist.add(item); _itemcount++; } public RSSItem getItem(int location) { return _itemlist.get(location); } public int getItemCount() { return _itemcount; } }
step 6: Now we need to create a Parser class where it takes the url and performs the Parsing. We write some conditions to check for the tags and we set the RSSItems and finally the RSSFeed.
In this example we use DOM Parser to Parse the XML data. We create a class called ‘DOMParser’ in which we create a method of type RSSFeed called parseXML(String xml) which takes in a url as parameter. Once the Parsing is done the method returns the feed(RSSFeed) object:
private RSSFeed _feed = new RSSFeed(); public RSSFeed parseXml(String xml) { // Parser the XML and Return the RSSFeed Object(_feed). return _feed; }
Here is the final ‘DOMParser.java‘ :
public class DOM_Parser { private RSSFeed _feed = new RSSFeed(); public RSSFeed parseXml(String xml) { URL url = null; try { url = new URL(xml); } catch (MalformedURLException e1) { e1.printStackTrace(); } try { // Create required instances DocumentBuilderFactory dbf; dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); // Parse the xml Document doc = db.parse(new InputSource(url.openStream())); doc.getDocumentElement().normalize(); // Get all tags. NodeList nl = doc.getElementsByTagName("item"); int length = nl.getLength(); for (int i = 0; i < length; i++) { Node currentNode = nl.item(i); RSSItem _item = new RSSItem(); NodeList nchild = currentNode.getChildNodes(); int clength = nchild.getLength(); // Get the required elements from each Item for (int j = 1; j < clength; j = j + 2) { Node thisNode = nchild.item(j); String theString = null; String nodeName = thisNode.getNodeName(); theString = nchild.item(j).getFirstChild().getNodeValue(); if (theString != null) { if ("title".equals(nodeName)) { // Node name is equals to 'title' so set the Node // value to the Title in the RSSItem. _item.setTitle(theString); } else if ("description".equals(nodeName)) { _item.setDescription(theString); // Parse the html description to get the image url String html = theString; org.jsoup.nodes.Document docHtml = Jsoup .parse(html); Elements imgEle = docHtml.select("img"); _item.setImage(imgEle.attr("src")); } else if ("pubDate".equals(nodeName)) { // We replace the plus and zero's in the date with // empty string String formatedDate = theString.replace(" +0000", ""); _item.setDate(formatedDate); } } } // add item to the list _feed.addItem(_item); } } catch (Exception e) { } // Return the final feed once all the Items are added to the RSSFeed // Object(_feed). return _feed; } }
step 7: Now we have our Parser ready. In the Splash Activity we now call the method parseXml(String xml) from the DOMParser class with the URL we got(http://www.mobilenations.com/rss/mb.xml) as the parameter:
DOMParser myParser = new DOMParser(); feed = myParser.parseXml("http://www.mobilenations.com/rss/mb.xml");
Once after the feed has been obtained in the onPostExecute() method we need to start the ListActivity and pass it the feed we got, we do that by bundling the feed object and attaching it to the Intent:
Bundle bundle = new Bundle(); bundle.putSerializable("feed", feed); // launch List activity Intent intent = new Intent(SplashActivity.this, ListActivity.class); intent.putExtras(bundle); startActivity(intent);
step 8: The final ‘SplashActivity.java‘ will looks as follows:
public class SplashActivity extends Activity { private String RSSFEEDURL = "http://www.mobilenations.com/rss/mb.xml"; RSSFeed feed; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.splash); ConnectivityManager conMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (conMgr.getActiveNetworkInfo() == null && !conMgr.getActiveNetworkInfo().isConnected() && !conMgr.getActiveNetworkInfo().isAvailable()) { // No connectivity - Show alert AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage( "Unable to reach server, \nPlease check your connectivity.") .setTitle("TD RSS Reader") .setCancelable(false) .setPositiveButton("Exit", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { finish(); } }); AlertDialog alert = builder.create(); alert.show(); } else { // Connected - Start parsing new AsyncLoadXMLFeed().execute(); } } private class AsyncLoadXMLFeed extends AsyncTask { @Override protected Void doInBackground(Void... params) { // Obtain feed DOM_Parser myParser = new DOM_Parser(); feed = myParser.parseXml(RSSFEEDURL); return null; } @Override protected void onPostExecute(Void result) { super.onPostExecute(result); Bundle bundle = new Bundle(); bundle.putSerializable("feed", feed); // launch List activity Intent intent = new Intent(SplashActivity.this, ListActivity.class); intent.putExtras(bundle); startActivity(intent); // kill this activity finish(); } } }
Custom List Activity:
step 9: To display the feed items in a list we need to create a custom List View with each List item containing a title, a thumbnail image & a date. Creating a custom list view has been posted in the previous tutorial titled: Android simple & custom List views with examples.
Please refer the above mentioned post to understand how to create a custom list view.
A screen shot of the Custom List that we are going to achieve is shown below:
Here are the files i used to create the above Custom List View:
Layout files:
‘feed_list.xml‘:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@+id/listView" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" > </ListView> </LinearLayout>
‘list_item.xml‘:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="2dp" > <ImageView android:id="@+id/thumb" android:layout_width="60dp" android:layout_height="60dp" android:contentDescription="@string/app_name" /> <RelativeLayout android:layout_width="wrap_content" android:layout_height="60dp" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_toLeftOf="@+id/arrow" android:layout_toRightOf="@+id/thumb" android:orientation="vertical" > <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="2" android:text="@string/title" android:textSize="16dp" android:textStyle="bold" /> <TextView android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_marginTop="5dp" android:padding="2dp" android:text="@string/date" android:textColor="#7F7F7F" android:textSize="10dp" /> </RelativeLayout> <ImageView android:id="@+id/arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="5dp" android:contentDescription="@string/app_name" android:src="@drawable/arrow_next" /> </RelativeLayout>
‘ListActivity.java‘:
public class ListActivity extends Activity { Application myApp; RSSFeed feed; ListView lv; CustomListAdapter adapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.feed_list); myApp = getApplication(); // Get feed form the file feed = (RSSFeed) getIntent().getExtras().get("feed"); // Initialize the variables: lv = (ListView) findViewById(R.id.listView); lv.setVerticalFadingEdgeEnabled(true); // Set an Adapter to the ListView adapter = new CustomListAdapter(this); lv.setAdapter(adapter); // Set on item click listener to the ListView lv.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { // actions to be performed when a list item clicked int pos = arg2; Bundle bundle = new Bundle(); bundle.putSerializable("feed", feed); Intent intent = new Intent(ListActivity.this, DetailActivity.class); intent.putExtras(bundle); intent.putExtra("pos", pos); startActivity(intent); } }); } @Override protected void onDestroy() { super.onDestroy(); adapter.imageLoader.clearCache(); adapter.notifyDataSetChanged(); } class CustomListAdapter extends BaseAdapter { private LayoutInflater layoutInflater; public ImageLoader imageLoader; public CustomListAdapter(ListActivity activity) { layoutInflater = (LayoutInflater) activity .getSystemService(Context.LAYOUT_INFLATER_SERVICE); imageLoader = new ImageLoader(activity.getApplicationContext()); } @Override public int getCount() { // Set the total list item count return feed.getItemCount(); } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { // Inflate the item layout and set the views View listItem = convertView; int pos = position; if (listItem == null) { listItem = layoutInflater.inflate(R.layout.list_item, null); } // Initialize the views in the layout ImageView iv = (ImageView) listItem.findViewById(R.id.thumb); TextView tvTitle = (TextView) listItem.findViewById(R.id.title); TextView tvDate = (TextView) listItem.findViewById(R.id.date); // Set the views in the layout imageLoader.DisplayImage(feed.getItem(pos).getImage(), iv); tvTitle.setText(feed.getItem(pos).getTitle()); tvDate.setText(feed.getItem(pos).getDate()); return listItem; } } }
In the above List Activity in the Adapter class to set the thumbnail image we used the ‘ImageLoader’ Class which we copied to Project earlier along with some other supporting classes.
And in the onItemClickListner(), we are Passing the feed along with the position of the item clicked to the ‘DetailActivity’.
step 10: So lets now create the ‘DetailActivity’ which takes in the feed and position to display the title and the full description in a WebView.
The Layout used in detail Activity should contain a TextView to hold the title and a WebView to hold the description. (As the description we got from the parser is a HTML text we need the WebView to display the description instead of a TextView.)
The final ‘detail.xml‘ will looks like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="2dp" > <ImageView android:id="@+id/thumb" android:layout_width="60dp" android:layout_height="60dp" android:contentDescription="@string/app_name" /> <RelativeLayout android:layout_width="wrap_content" android:layout_height="60dp" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_toLeftOf="@+id/arrow" android:layout_toRightOf="@+id/thumb" android:orientation="vertical" > <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" android:maxLines="2" android:text="@string/title" android:textSize="16dp" android:textStyle="bold" /> <TextView android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_marginTop="5dp" android:padding="2dp" android:text="@string/date" android:textColor="#7F7F7F" android:textSize="10dp" /> </RelativeLayout> <ImageView android:id="@+id/arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="5dp" android:contentDescription="@string/app_name" android:src="@drawable/arrow_next" /> </RelativeLayout>
Here’s the result of the above layout:
In the ‘DetailActivity’ we created, we need to get the feed and the position that has been passed from the List Activiy. We can get the Intent data by using getIntent() method:
RSSFeed feed = (RSSFeed) getIntent().getExtras().get("feed"); int pos = getIntent().getExtras().getInt("pos");
And once we got the feed and the Position we can set the views in the Layout. The final ‘DetailActivity’ will looks like this:
public class DetailActivity extends Activity { RSSFeed feed; TextView title; WebView desc; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.detail); // Enable the vertical fading edge (by default it is disabled) ScrollView sv = (ScrollView) findViewById(R.id.sv); sv.setVerticalFadingEdgeEnabled(true); // Get the feed object and the position from the Intent feed = (RSSFeed) getIntent().getExtras().get("feed"); int pos = getIntent().getExtras().getInt("pos"); // Initialize the views title = (TextView) findViewById(R.id.title); desc = (WebView) findViewById(R.id.desc); // set webview properties WebSettings ws = desc.getSettings(); ws.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); ws.getPluginState(); ws.setPluginState(PluginState.ON); ws.setJavaScriptEnabled(true); ws.setBuiltInZoomControls(true); // Set the views title.setText(feed.getItem(pos).getTitle()); desc.loadDataWithBaseURL("http://www.androidcentral.com/", feed .getItem(pos).getDescription(), "text/html", "UTF-8", null); } }
step 11: Do not forget to register the Activities in the Manifest and also set the required permissions:
The final Manifest will look like this:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.td.rssreader" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".SplashActivity" android:theme="@android:style/Theme.Light.NoTitleBar.Fullscreen" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".ListActivity" /> <activity android:name=".DetailActivity" /> </application> </manifest>
We are done now! Run the Application!
In up coming tutorials we talk about bringing this ‘Simple RSS Reader’ to a beautiful RSS Reader Application by adding the features like: Offline Reading, pull to refresh List, Swipe through the Details, Action Bar, Font size increase and decrease in the description view and more other miscellaneous features.