simple_rss_featured

For this Android developer tutorial, we are going to create a simple RSS reader app. This app would fetch RSS feeds from a single, user specified internet address, and display the contents using a RecyclerView. If you aren’t familiar with the RecyclerView widget, check out our previous tutorial on using a RecyclerView.

RSS, or Rich Site Summary, is an easy and efficient way to share snippets of frequently updated information such as blog entries or news items with subscribers. Almost every news website today has an RSS feed, and these feeds can be accessed through dedicated readers or through your favorite browser. An RSS feed usually contains a summary of the new content, rather than the entire news (or blog) article. To answer your question, yes, Android Authority has an RSS feed, available at http://feed.androidauthority.com/, and you should subscribe, if you haven’t already. :D

Sample RSS Feed

An RSS feed is a plain text document, formatted in XML. Being XML, it is relatively easy for humans to read, and also not difficult for programs to parse. A sample XML document is shown below.

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
 <title>RSS Title</title>
 <description>This is an example of an RSS feed</description>
 <link>http://www.example.com/main.html</link>
 <lastBuildDate>Mon, 06 Sep 2010 00:01:00 +0000 </lastBuildDate>
 <pubDate>Sun, 06 Sep 2009 16:20:00 +0000</pubDate>
 <ttl>1800</ttl>

 <item>
  <title>Example entry</title>
  <description>Here is some text containing an interesting description.</description>
  <link>http://www.example.com/blog/post/1</link>
  <guid isPermaLink="true">7bd204c6-1655-4c27-aeee-53f933c5395f</guid>
  <pubDate>Sun, 06 Sep 2009 16:20:00 +0000</pubDate>
 </item>

 <item>
  <title>Second example entry</title>
  <description>More text containing even more interesting description.</description>
  <link>http://www.example.com/blog/post/2</link>
  <guid isPermaLink="true">7a4a56b6-1655-4c27-aeee-33e4453f2675</guid>
  <pubDate>Sun, 06 Sep 2009 18:14:00 +0000</pubDate>
 </item>

</channel>
</rss>

We can see from the sample above, the feed has a title, a description, link, last build date, a publish date and a time to live. For this tutorial, we are only interested in the title, description and link.

There are also two items in the feed, and each has a title, a description and a link, along with a guid (globally unique identifier) and publish date. Most feed items have other tags, but, for our simple RSS  reader, we are only concerned with the title, description and link.

Preparing the project

Of course, the first thing we will do is create a new Android Studio project with an empty activity, which we’ve named SimpleRSSReader. In the AndroidManifiest.xml file, we include the INTERNET permission. Since the rss reader will fetch feeds from the internet, we must declare this permission.

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

Also, we intend to use the support library, RecyclerView and design support library, so we add those to our app build.gradle file. The latest version as at publication is 25.0.1.

    compile 'com.android.support:appcompat-v7:25.0.1'
    compile 'com.android.support:recyclerview-v7:25.0.1'
    compile 'com.android.support:design:25.0.1'

Great. With the initial setup complete, let’s get to work.

Design the layout

For our simple app, we want the user to enter a target RSS feed url, and when he clicks a button, our rss reader app will fetch the feed and display the feed items in a recyclerview.

simple_rss_aa_fetched

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.sample.foo.simplerssreader.MainActivity">

    <Button
        android:id="@+id/fetchFeedButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:text="Fetch" />

    <android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginEnd="@dimen/activity_horizontal_margin"
        android:layout_toStartOf="@id/fetchFeedButton"
        android:hint="Rss feed source">

        <EditText
            android:id="@+id/rssFeedEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.design.widget.TextInputLayout>

    <TextView
        android:id="@+id/feedTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/textInputLayout"
        android:text="Feed Title: " />

    <TextView
        android:id="@+id/feedDescription"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/feedTitle"
        android:text="Feed Description: " />

    <TextView
        android:id="@+id/feedLink"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/feedDescription"
        android:text="Feed Link: " />

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/feedLink"
        android:layout_marginTop="@dimen/activity_vertical_margin">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>

simple_rss_layout

Did you notice that we enclosed our recyclerview within a SwipeRefreshLayout? A SwipeRefreshLayout is a design support library widget that allows users pull down to refresh, and shows a nice circular progress bar.

simple_rss_swipe_refresh

Creating the MainActivity

Within the MainActivity class, the first thing we do is define references to the EditText, the Button, RecyclerView and SwipeLayout. We also setup a click listener for the Button, and an OnRefreshListener for the SwipeRefreshLayout.

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private EditText mEditText;
    private Button mFetchFeedButton;
    private SwipeRefreshLayout mSwipeLayout;
    private TextView mFeedTitleTextView;
    private TextView mFeedLinkTextView;
    private TextView mFeedDescriptionTextView;

    private List<RssFeedModel> mFeedModelList;
    private String mFeedTitle;
    private String mFeedLink;
    private String mFeedDescription;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        mEditText = (EditText) findViewById(R.id.rssFeedEditText);
        mFetchFeedButton = (Button) findViewById(R.id.fetchFeedButton);
        mSwipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
        mFeedTitleTextView = (TextView) findViewById(R.id.feedTitle);
        mFeedDescriptionTextView = (TextView) findViewById(R.id.feedDescription);
        mFeedLinkTextView = (TextView) findViewById(R.id.feedLink);

        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        mFetchFeedButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new FetchFeedTask().execute((Void) null);
            }
        });
        mSwipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                new FetchFeedTask().execute((Void) null);
            }
        });
    }
}

Both the Button and SwipeRefreshLayout perform the same action, which is to start a new FetchFeedTask. The FetchFeedTask is an AsyncTask, which allows us perform possible long running tasks in a background thread. If you try to fetch feeds from a url on the main thread, your app will become unresponsive, and can appear to the user to have stopped working. Ever since Ice-cream Sandwich, Android no longer allows apps make network calls on the main thread. For a refresher on using AsyncTask, you can check out our previous article that discussed how to use a web API from your Android app.

The FetchFeedTask has three simple methods:

  1. onPreExecute, which is called before the long running task starts, and is executed on the calling thread.
  2. doInBackground, which is the long running task, and is executed in a new thread
  3. onPostExecute, which is called after the long running task is complete, and is executed on the calling thread.

Since Android allows manipulation of UI elements (Buttons, SwipeRefreshLayout, etc) only on the UI/Main thread, we can update the UI both in onPreExecute, and onPostExecute, but not in doInBackground.

private class FetchFeedTask extends AsyncTask<Void, Void, Boolean> {

        private String urlLink;

        @Override
        protected void onPreExecute() {
            mSwipeLayout.setRefreshing(true);
            urlLink = mEditText.getText().toString();
        }

        @Override
        protected Boolean doInBackground(Void... voids) {
            if (TextUtils.isEmpty(urlLink))
                return false;

            try {
                if(!urlLink.startsWith("http://") && !urlLink.startsWith("https://"))
                    urlLink = "http://" + urlLink;

                URL url = new URL(urlLink);
                InputStream inputStream = url.openConnection().getInputStream();
                mFeedModelList = parseFeed(inputStream);
                return true;
            } catch (IOException e) {
                Log.e(TAG, "Error", e);
            } catch (XmlPullParserException e) {
                Log.e(TAG, "Error", e);
            }
            return false;
        }

        @Override
        protected void onPostExecute(Boolean success) {
            mSwipeLayout.setRefreshing(false);

            if (success) {
                mFeedTitleTextView.setText("Feed Title: " + mFeedTitle);
                mFeedDescriptionTextView.setText("Feed Description: " + mFeedDescription);
                mFeedLinkTextView.setText("Feed Link: " + mFeedLink);
                // Fill RecyclerView
                mRecyclerView.setAdapter(new RssFeedListAdapter(mFeedModelList));
            } else {
                Toast.makeText(MainActivity.this,
                        "Enter a valid Rss feed url",
                        Toast.LENGTH_LONG).show();
            }
        }
    }

In the code snippet above, you can see that we only update the UI in onPreExecute and onPostExecute, while all the computation is done within doInBackground. We define FetchFeedTask as an inner class in MainActivity, so that we can access the MainActivity layout widgets.

In doInBackground, we first confirm that the url entered is not empty, then we add a ‘http://’ to the url if the user left it out. The next step is to open a url connection, using the url.getConnection().getInputStream() method. Finally, we call a method named parseFeed() to parse the resulting RSS feed.

Parsing the RSS feed

Thankfully, android contains some utility classes and interfaces to handle XML parsing. For our rss reader, we will use an XmlPullParser instance to handle parsing the feed. XmlPullParser can have either an InputStream or a Reader as the source of data. We simply read each XML tag in the RSS feed until we get to the end of the document. You can see in the code snippet below, that we are only interested in the XML tag if it is either title, description or link.

public List<RssFeedModel> parseFeed(InputStream inputStream) throws XmlPullParserException,
                                                                    IOException {
        String title = null;
        String link = null;
        String description = null;
        boolean isItem = false;
        List<RssFeedModel> items = new ArrayList<>();

        try {
            XmlPullParser xmlPullParser = Xml.newPullParser();
            xmlPullParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
            xmlPullParser.setInput(inputStream, null);

            xmlPullParser.nextTag();
            while (xmlPullParser.next() != XmlPullParser.END_DOCUMENT) {
                int eventType = xmlPullParser.getEventType();

                String name = xmlPullParser.getName();
                if(name == null)
                    continue;

                if(eventType == XmlPullParser.END_TAG) {
                    if(name.equalsIgnoreCase("item")) {
                        isItem = false;
                    }
                    continue;
                }

                if (eventType == XmlPullParser.START_TAG) {
                    if(name.equalsIgnoreCase("item")) {
                        isItem = true;
                        continue;
                    }
                }

                Log.d("MyXmlParser", "Parsing name ==> " + name);
                String result = "";
                if (xmlPullParser.next() == XmlPullParser.TEXT) {
                    result = xmlPullParser.getText();
                    xmlPullParser.nextTag();
                }

                if (name.equalsIgnoreCase("title")) {
                    title = result;
                } else if (name.equalsIgnoreCase("link")) {
                    link = result;
                } else if (name.equalsIgnoreCase("description")) {
                    description = result;
                }

                if (title != null && link != null && description != null) {
                    if(isItem) {
                        RssFeedModel item = new RssFeedModel(title, link, description);
                        items.add(item);
                    }
                    else {
                        mFeedTitle = title;
                        mFeedLink = link;
                        mFeedDescription = description;
                    }

                    title = null;
                    link = null;
                    description = null;
                    isItem = false;
                }
            }

            return items;
        } finally {
            inputStream.close();
        }
    }

RssFeedModel is a simple helper class we created to hold title, description and link information for an RSS feed item.

public class RssFeedModel {

    public String title;
    public String link;
    public String description;

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

Displaying the feeds

At this point, we are ready to fetch, and parse RSS feeds. However, we still need to display the results in our rss reader RecyclerView. Recall that in the onPostExecute() method above, there is the line:

mRecyclerView.setAdapter(new RssFeedListAdapter(mFeedModelList));

Once again, if you haven’t used a RecyclerView before, check out our previous tutorial on Using RecyclerView. It is very easy and straightforward to use. To display each feed item, we create a layout file, called item_rss_feed.xml.

simple_rss_item_feed

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/titleText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/descriptionText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/linkText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/colorAccent" />
</LinearLayout>

And finally, the RssFeedListAdapter.

public class RssFeedListAdapter
        extends RecyclerView.Adapter<RssFeedListAdapter.FeedModelViewHolder> {

    private List<RssFeedModel> mRssFeedModels;

    public static class FeedModelViewHolder extends RecyclerView.ViewHolder {
        private View rssFeedView;

        public FeedModelViewHolder(View v) {
            super(v);
            rssFeedView = v;
        }
    }

    public RssFeedListAdapter(List<RssFeedModel> rssFeedModels) {
        mRssFeedModels = rssFeedModels;
    }

    @Override
    public FeedModelViewHolder onCreateViewHolder(ViewGroup parent, int type) {
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_rss_feed, parent, false);
        FeedModelViewHolder holder = new FeedModelViewHolder(v);
        return holder;
    }

    @Override
    public void onBindViewHolder(FeedModelViewHolder holder, int position) {
        final RssFeedModel rssFeedModel = mRssFeedModels.get(position);
        ((TextView)holder.rssFeedView.findViewById(R.id.titleText)).setText(rssFeedModel.title);
        ((TextView)holder.rssFeedView.findViewById(R.id.descriptionText))
                                                             .setText(rssFeedModel.description);
        ((TextView)holder.rssFeedView.findViewById(R.id.linkText)).setText(rssFeedModel.link);
    }

    @Override
    public int getItemCount() {
        return mRssFeedModels.size();
    }
}

simple_rss_just_xkcd

Wrap up

As always, the complete source is available on github. You can look into styling your item_rss_feed.xml, displaying more content than just the item title, description and link, and/or, for the adventurous, writing your own RSS news aggreagator with multiple feeds from multiple sources.

Happy coding.