Story of coding ITCutiesApp for Android #1 – parse ATOM, modify HTML input, custom ListView, splash screen, load data in background

This is the story of coding the ITCutiesApp for Android platform which is available at Google’s Play page. Our application gives you access to all the posts available on our page via RSS ATOM feed.

ITCutiesApp   ITCutiesApp   ITCutiesApp   ITCutiesApp

In this post we will write about problems we had while coding the 1.0 version of App and how we have solved them. In this article you will find following topics:
- Parse RSS ATOM data
- Modify incoming HTML input
- Building custom ListView
- Opening post data in the App
- Building splash screen
- Loading data in the background – real time ProgressBar update

Here is how the project structure looks like.
ITCutiesApp - Eclipse project structure


Parse RSS ATOM data


In our application we have used the same mechanism we have presented in our How to write Android ATOM parser application tutorial which are as described below. We are reading data from http://www.itcuties.com/feed/atom ATOM feed.

RssAtomItem.java

package com.itcuties.app.reader.data;


/**
 * This class represents an ATOM entity - a post
 * 
 * @author ITCuties
 *
 */
public class RssAtomItem {

	private String title;
	
	private String content;

	private String publishDate;
	
	private String category;
	
	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public String getPublishDate() {
		return publishDate;
	}

	public void setPublishDate(String publishDate) {
		this.publishDate = publishDate;
	}

	public String getCategory() {
		return category;
	}

	public void setCategory(String category) {
		this.category = category;
	}
}

This class is simple, it represents one post data – post title, text, publish date and category.

RssAtomReader.java

package com.itcuties.app.util.atom;

import java.util.List;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import android.widget.ProgressBar;

import com.itcuties.app.reader.data.RssAtomItem;

/**
 * Class reads RSS ATOM data.
 * 
 * @author ITCuties
 *
 */
public class RssAtomReader {
	
	private String rssUrl;

	// This reference needs to be provided to 
	// the SAX Handler for update.
	private ProgressBar progressBar;

	/**
	 * Constructor
	 * 
	 * @param rssUrl
	 */
	public RssAtomReader(String rssUrl) {
		this.rssUrl = rssUrl;
	}

	public void setProgressBar(ProgressBar progressBar) {
		this.progressBar = progressBar;
	}
	
	/**
	 * Get RSS items.
	 * 
	 * @return
	 */
	public List<RssAtomItem> getItems() throws Exception {
		// SAX parse RSS data
		SAXParserFactory factory = SAXParserFactory.newInstance();
		SAXParser saxParser = factory.newSAXParser();

		// Create our handler
		RssAtomParseHandler handler = new RssAtomParseHandler();
		handler.setProgressBar(progressBar);
		
		// Parse the stream
		saxParser.parse(rssUrl, handler);
		
		return handler.getItems();
		
	}
}

This class reads and parses data out of the given ATOM feed URL. We are using SAX parser which is a stream parser. This means that data is being read as the stream is being parsed. SAX parser is slower than DOM parser but it doesn’t need the whole stream to be loaded to the memory as the DOM parser does. This means that our application uses less memory than it would when the DOM parser would be used. Our code creates SAX parser and parses the stream read from given URL using our custom written handler – RssAtomParseHandler. Data read is stored in the handler class as a list of RssAtomItem elements.

RssAtomParseHandler.java

package com.itcuties.app.util.atom;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import android.util.Log;
import android.widget.ProgressBar;

import com.itcuties.app.reader.data.RssAtomItem;
import com.itcuties.app.util.text.TextConverter;

/**
 * SAX tag handler
 * 
 * @author ITCuties
 *
 */
public class RssAtomParseHandler extends DefaultHandler {

	private List<RssAtomItem> rssItems;
	
	// Used to reference item while parsing
	private RssAtomItem currentItem;
	
	// Parsing title indicator
	private boolean parsingTitle;
	// Parsing contents indicator
	private boolean parsingContents;
	// Parsing published date indicator
	private boolean parsingPublishedDate;
	// A buffer for title contents
	private StringBuffer currentTitleSb;
	// A buffer for content tag contents
	private StringBuffer currentContentSb;
	// A buffer for publish date tag contents;
	private StringBuffer currentPubishDateSb;
	
	// This is a ProgressBar reference set by the SplashScreen
	// We use it to show the real progress while each item from 
	// the feed is read.
	private ProgressBar progressBar;
	
	public RssAtomParseHandler() {
		rssItems = new ArrayList<RssAtomItem>();
	}
	
	public List<RssAtomItem> getItems() {
		return rssItems;
	}
	
	public void setProgressBar(ProgressBar progressBar) {
		this.progressBar = progressBar;
	}

	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		if ("entry".equals(qName)) {
			currentItem = new RssAtomItem();
		} else if ("title".equals(qName)) {
			parsingTitle = true;
			currentTitleSb = new StringBuffer();
		} else if ("content".equals(qName)) {
			parsingContents = true;
			currentContentSb = new StringBuffer();
		} else if ("published".equals(qName)) {
			parsingPublishedDate = true;
			currentPubishDateSb = new StringBuffer();
		} else if ("category".equals(qName)) {
			if (currentItem != null && currentItem.getCategory() == null) {
				currentItem.setCategory(attributes.getValue("term"));
			}
		}
	}
	
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		if ("entry".equals(qName)) {
			rssItems.add(currentItem);
			currentItem = null;
		} else if ("title".equals(qName)) {
			parsingTitle = false;
			
			// There is a title tag for a whole channel present. 
			// It is being parsed before the entry tag is present, 
			// so we need to check if item is not null
			if (currentItem != null)
				// We encode the title so that it can be read by the application properly
				currentItem.setTitle(TextConverter.convertTitle(currentTitleSb.toString()));
			
		} else if ("content".equals(qName)) {
			parsingContents = false;
			
			if (currentItem != null) {
				// When an item's content is being set we convert it a little bit:
				// - All styles and JavaScript information are removed
				// - Content is URL encoded so that the WebView component can display it correctly
				// - We remove two sections which are added by the ATOM feed, the post info section
				//	 and the code download section (only github link is present in the text)
				currentItem.setContent(TextConverter.clearStylesAndJS(
									   		TextConverter.URLEncode(
									   				TextConverter.removePostInfoSection(
									   						TextConverter.removeDownloadSection(currentContentSb.toString())
													)
									   		)
									   ));
				
				// Update progressBar
				progressBar.setProgress(progressBar.getProgress()+1);
				
			}
			
		} else if ("published".equals(qName)) {
			parsingPublishedDate = false;
			
			if (currentItem != null)
				try {
					// We convert the date from the WordPress format to DD-MM-YYYY format
					currentItem.setPublishDate(TextConverter.convertDate(currentPubishDateSb.toString()));
				} catch (ParseException e) {
					currentItem.setPublishDate("no date");
					Log.e("ITCRssReader", "No publish date available");
				}
			
		}
	}
	
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		// title, contents and date tags contents values are set here.
		// This method can be called multiple times for one tag, it depends
		// on how much text tag's contents has. Remember that SAX is a 
		// stream parser so it parses feed stream sequentially.
		if (parsingTitle) {
			if (currentItem != null)
				currentTitleSb.append(new String(ch, start, length));
		} else if (parsingContents) {
			if (currentItem != null)
				currentContentSb.append(new String(ch, start, length));
		} else if (parsingPublishedDate) {
			if (currentItem != null)
				currentPubishDateSb.append(new String(ch, start, length));
		}
	}	
}

This the place in the code where all the magic of reading data from the ATOM feed takes place. This is the SAX parser handler. The startElement is called when handler reaches XML opening tag in the stream, endElement method is called when handler reaches XML closing tag. characters method is called when parser is reading tag contents. Notice that this method may be called multiple times in some cases depending on the data length present in a tag’s content.


Modify incoming HTML input


You may have noticed that we are modifying each post’s title, publish date and body while this data is being read from the RSS ATOM feed – it is done in the endElement method of the RssAtomParseHandler. Here is our helper class that is used to modify incoming data.

TextConverter.java

package com.itcuties.app.util.text;

import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Locale;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.NodeTraversor;

import com.itcuties.app.util.jsoup.JSoupNodeVisitor;

/**
 * Class deals with unwanted text parts that come from the RSS Atom Feed.
 * 
 * @author ITCuties
 *
 */
public class TextConverter {

	// Text conversion map
	private static HashMap<String, String> conversions = new HashMap<String, String>();
	
	// Map initialization
	static {
		conversions.put("&#8211;", "-");
		conversions.put("&#8217;", "'");
	}
	
	/**
	 * Perform conversion of a post title.
	 * @param text
	 * @return
	 */
	public static String convertTitle(String text) {
		// Remove unwanted signs based on the conversions map
		String[] splittedText = text.trim().split(" ");
		StringBuffer sb = new StringBuffer();
		
		for (String textWord: splittedText) {
			for (String cKey: conversions.keySet())
				textWord = textWord.replaceAll(cKey, conversions.get(cKey));
				
			sb.append(textWord).append(" ");
		}
		
		return sb.toString();
		
	}
	
	
	// Date formatter 2013-07-11T04:29:14Z
	private static SimpleDateFormat dateFormatterIn = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH);
	// Date formater 27-07-2013
	private static SimpleDateFormat dateFormatterOut= new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
	/**
	 * Format date to the format like this: 27-07-2013
	 * @param stringDate
	 * @return
	 * @throws ParseException
	 */
	public static String convertDate(String stringDate) throws ParseException {
		return dateFormatterOut.format(dateFormatterIn.parse(stringDate));
	}
	
	// Download section markers
	private static String downloadSectionIconHtmlCodeStart = "<p><img class='size-full wp-image-967 alignleft'";
	private static String downloadSectionTextHtmlCodeStart = "<p><strong>Download this sample code ";
	private static String downloadSectionEnd = "</p>";
	/**
	 * Removes download section from the post text.
	 * @param postText
	 * @return
	 */
	public static String removeDownloadSection(String postText) {
		int downloadSectionIconIndex = postText.indexOf(downloadSectionIconHtmlCodeStart);
		if (downloadSectionIconIndex != -1)
			postText = postText.replaceAll(postText.substring(downloadSectionIconIndex, postText.indexOf(downloadSectionEnd, downloadSectionIconIndex) + downloadSectionEnd.length()),"");
		
		int downloadSectionTextIndex = postText.indexOf(downloadSectionTextHtmlCodeStart);
		if (downloadSectionTextIndex != -1)
			postText = postText.replaceAll(postText.substring(downloadSectionTextIndex, postText.indexOf(downloadSectionEnd, downloadSectionTextIndex) + downloadSectionEnd.length()),"");
		
		return postText;
	}
	
	// Post info section markers
	private static String postInfoSectionHtmlCodeStart = "<p>The post <a";
	private static String postInfoSectionEnd = "</p>";
	/**
	 * Remove post info section from the post text.
	 * @param postText
	 * @return
	 */
	public static String removePostInfoSection(String postText) {
		int postInfoSectionIndex = postText.indexOf(postInfoSectionHtmlCodeStart);
		if (postInfoSectionIndex != -1)
			postText = postText.replaceAll(postText.substring(postInfoSectionIndex, postText.indexOf(postInfoSectionEnd, postInfoSectionIndex) + postInfoSectionEnd.length()),"");
		
		return postText;
	}
	
	/**
	 * URL Encode string.
	 * @param postText
	 * @return
	 */
	public static String URLEncode(String postText) {
		return URLEncoder.encode(postText).replaceAll("\\+", "%20");
	}
	
	/**
	 * Remove all the styles and JavaScript from HTML post text
	 * @param postText
	 * @return
	 */
	public static String clearStylesAndJS(String postText) {
		Document doc = Jsoup.parse(postText);
		
		doc.select("javascript").remove();
		
		NodeTraversor traversor  = new NodeTraversor(new JSoupNodeVisitor());

		traversor.traverse(doc.body());

		return doc.toString();
	}
}

The first interesting method is the convertDate method. This method is used to convert post’s publish date to the dd-MM-yyyy format. To learn more about Java Date conversions please visit our other tutorial – Java Date conversion.
To remove parts of the text that we don’t want to display we simply use String.replaceAll method – see removeDownloadSection and removePostInfoSection methods.
The URLEncode method is used to encode the post’s body content. We are doing this because WebView which is used to render this data acts better when data displayed is URL encoded.
The last important method here is the clearStylesAndJS method. It is used to remove all the styling CSS and JavaScript code from the post contents. We are using JSoup library to modify HTML data. This method uses JSoupNodeVisitor to do it’s job.

JSoupNodeVisitor.java

package com.itcuties.app.util.jsoup;

import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.NodeVisitor;

/**
 * JSoup's node visitor used to remove class and style
 * information from the HTML post text.
 * 
 * @author ITCuties
 *
 */
public class JSoupNodeVisitor implements NodeVisitor {

	public void tail(Node node, int depth) {
		if (node instanceof Element) {
			Element e = (Element) node;
			e.removeAttr("class");
			e.removeAttr("style");
		}
	}

	public void head(Node node, int arg1) {
	}
}

If you want to know how to use JSoup library in your android project visit our Ask&Answer forumUsing JSoup in Android project.


Building custom ListView

item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="match_parent"
    android:background="#fff"
    android:orientation="vertical" >

    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TableRow
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <ImageView
                android:id="@+id/imageViewCategoryIcon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:src="@drawable/ic_launcher" />

            <TableLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" >

                <TableRow
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:minWidth="1dp">

                    <TextView
                        android:id="@+id/textViewTitle"
                        android:layout_width="270dp"
                        android:layout_height="wrap_content"
                        android:padding="5dp"
                        android:text="[TITLE-GOES-HERE]"
                        android:textColor="#000"
                        android:textStyle="bold"/>

                </TableRow>

                <TableRow
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" >

                    <TextView
                        android:id="@+id/textViewPublishDate"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:padding="5dp"
                        android:text="[PUB-DATE-GOES-HERE]"
                        android:textAppearance="?android:attr/textAppearanceSmall" />

                </TableRow>

            </TableLayout>

        </TableRow>

    </TableLayout>
    
</LinearLayout>

This is our item layout which we display in the list. It consist of an category icon, post text and post publish date. We are using TableLayout to place elements in the layout. This layout gives you quite a lot control over elements position.

ListPostsActivity.java

package com.itcuties.app;

import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;

import com.itcuties.app.adapters.ListAdapter;
import com.itcuties.app.reader.data.RssAtomItem;
import com.itcuties.app.reader.data.RssResults;

/**
 * This is our application main activity which displays list of posts.
 * 
 * @author ITCuties
 *
 */
public class ListPostsActivity extends ListActivity {
	
	/** 
	 * This method creates main application view
	 */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		// We set the list adapter giving it the results list downloaded from the ATOM feed
		setListAdapter(new ListAdapter(this, RssResults.getResults()));
		
	}
	
	/**
	 * On item click behavior
	 */
	protected void onListItemClick(ListView l, View v, int position, long id) {
		// When an item is clicked a Details activity needs to be started.
		Intent i = new Intent(this, DetailsActivity.class);
		
		// Set data for the details activity to display
		i.putExtra("title", ((RssAtomItem)getListAdapter().getItem(position)).getTitle());
		i.putExtra("content", ((RssAtomItem)getListAdapter().getItem(position)).getContent());
		i.putExtra("publishDate", ((RssAtomItem)getListAdapter().getItem(position)).getPublishDate());
		i.putExtra("category", ((RssAtomItem)getListAdapter().getItem(position)).getCategory());
		
		this.startActivity(i);
	}
}

This is the activity that displays the list of posts. It’s simple, because it extends ListActivity all we need to do in this code is to set list adapter by calling the setListAdapter method in the onCreate method body and code on item click behavior (onListItemClick method). We set the list adapter to our ListAdapter class which is described below. When an item is clicked we take post data from the clicked row and send it’s elements to the DetailsActivity which is started here as well.

ListAdapter.java

package com.itcuties.app.adapters;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.itcuties.app.R;
import com.itcuties.app.reader.data.RssAtomItem;
import com.itcuties.app.util.category.CategoryMapper;

/**
 * ListAdapter builds each element in the list.
 * @author ITCuties
 *
 */
public class ListAdapter extends ArrayAdapter<RssAtomItem> {

	// List context
    private final Context context;
    // List values
    private final List<RssAtomItem> items;

    public ListAdapter(Context context, List<RssAtomItem> items) {
    	// Set the layout for each item
        super(context, R.layout.item, items);
        this.context = context;
        this.items = items;
    }
    
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    	// Build each element of the list
    	LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View rowView = inflater.inflate(R.layout.item, parent, false);
        
        // Populate each layout element with the data from the items list
        TextView itemTitle = (TextView)rowView.findViewById(R.id.textViewTitle);
        itemTitle.setText(items.get(position).getTitle()); 
        
        TextView itemPublishDate = (TextView)rowView.findViewById(R.id.textViewPublishDate);
        itemPublishDate.setText(items.get(position).getPublishDate());
        
        // Set the icon base on the post's category
        ImageView icon = (ImageView)rowView.findViewById(R.id.imageViewCategoryIcon);
        icon.setImageResource(CategoryMapper.getIconIdForCategory(items.get(position).getCategory()));
        
        return rowView;

    }
}

This is the ListAdapter class. It uses the list of posts (RssAtomItem) read from the RSS ATOM feed of our page. This list, as well as the context of the calling activity is set in the constructor. Each row of the list is constructed by the getView method of our list adapter class. We are using layout inflater service to set each rows view. As you may have noticed we are setting icon of each list item based on the post’s category. We are using the CategoryMapper helper class which picks right icon for a given category.

CategoryMapper.java

package com.itcuties.app.util.category;

import java.util.HashMap;

import com.itcuties.app.R;

/**
 * Category to icon mapper.
 * 
 * @author ITCuties
 *
 */
public class CategoryMapper {

	// This is the mapping - Category name - icon
	private static HashMap<String, Integer> mapping = new HashMap<String, Integer>();
	
	static {
		// Mapping initialization
		mapping.put("Android", R.drawable.ic_android);
		mapping.put("Java", R.drawable.ic_java);
		mapping.put("J2EE", R.drawable.ic_j2ee);
		mapping.put("WordPress", R.drawable.ic_wordpress);
		mapping.put("Facebook", R.drawable.ic_facebook);
		mapping.put("Did you know", R.drawable.ic_didyouknow);
		mapping.put("SQL", R.drawable.ic_sql);
		mapping.put("Tools", R.drawable.ic_tools);
	}
	
	/**
	 * Get icon for the category name.
	 * @param category
	 * @return
	 */
	public static int getIconIdForCategory(String category) {
		if (mapping.get(category) != null)
			return mapping.get(category);
		
		// If no icon is found then return the default one - launcher icon
		return R.drawable.ic_launcher;
	}
}


Opening post data in the App


When you click on a list item a post is being displayed in a new activity. This behavior is coded in the onListItemClick method of the ListPostsActivity class where post’s data is set using Intent.putExtra method and the DetailsActivity is started.

DetailsActivity.java

package com.itcuties.app;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.TextView;

import com.itcuties.app.util.category.CategoryMapper;

/**
 * An activity displaying a ATOM entity details - the post
 * 
 * @author ITCuties
 *
 */
public class DetailsActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.details);
		
		// Read data to display
		String title 			= (String)getIntent().getExtras().get("title");
		String content 			= (String)getIntent().getExtras().get("content");
		String publishDate 		= (String)getIntent().getExtras().get("publishDate");
		String category 		= (String)getIntent().getExtras().get("category");
		
		// Set data in the layout
		TextView titleTV = (TextView)findViewById(R.id.textViewDetailsTitle);
		titleTV.setText(title);
		
		TextView publishDateTV = (TextView)findViewById(R.id.textViewDetailsPublishDate);
		publishDateTV.setText(publishDate);
		
		ImageView icon = (ImageView)findViewById(R.id.imageViewDetailsCategoryIcon);
		icon.setImageResource(CategoryMapper.getIconIdForCategory(category));
			
		WebView webView	 = (WebView)findViewById(R.id.webViewDetailsPostContents);
		webView.loadData(content, "text/html", "UTF-8");
		
	}
}

This is the DetailsActivity class code. Post data is being read and displayed in the layout elements – TextView is used to display texts, ImageView to display post’s category item and the WebView to display post contents.


Building splash screen


In our application we are using the same concept of building a splash screen as we have shown you in our earlier tutorial – How to create android splash screen. There is one difference however, we are using an AsyncTask and not a plain Thread to do the background job which is loading application data from the RSS ATOM feed described below.
There is one more important thing, we don’t want splash screen to rotate when the device rotates so we block this behavior in the AndroidManifest.xml file by using android:screenOrientation="portrait" setting.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.itcuties.app"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/>
    
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name="com.itcuties.app.SplashActivity"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.itcuties.app.ListPostsActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.itcuties.app.DetailsActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Our splash screen layout looks like this.

splash.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#fff">

        <ImageView
            android:id="@+id/imageViewLogo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:src="@drawable/ic_splash_logo" />

        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/imageViewLogo"
            android:layout_alignRight="@+id/imageViewLogo"
            android:layout_below="@+id/imageViewLogo"
            />

</RelativeLayout>


Loading data in the background – real time ProgressBar update


Our application displays the splash screen and reads RSS ATOM feed’s data in the background. While data is being read the progress bar is updated each time an item is parsed and added to the list. We use AsyncTaskclass for reading RSS data. Here is our SplashActivity code which is the launcher activity of our application (go back to AndroidManifest.xml and check it out)

SplashActivity.java

package com.itcuties.app;

import java.util.List;

import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.ProgressBar;

import com.itcuties.app.reader.data.RssAtomItem;
import com.itcuties.app.reader.data.RssResults;
import com.itcuties.app.util.atom.RssAtomReader;

/**
 * Application splash screen. It also loads data.
 * 
 * @author ITCuties
 *
 */
public class SplashActivity extends Activity {
	
	private ProgressBar progressBar;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		setContentView(R.layout.splash);
		
		progressBar = (ProgressBar)findViewById(R.id.progressBar);
		progressBar.setProgress(0); // No progress so far
		
		// Download data in the new thread
		GetRSSDataTask grdt = new GetRSSDataTask();
		grdt.execute("http://www.itcuties.com/feed/atom");
		
	}
	
	/**
	 * Read RSS channel data.
	 * 
	 * @author ITCuties
	 *
	 */
	private class GetRSSDataTask extends AsyncTask<String, Void, List<RssAtomItem> > {
		@Override
		protected List<RssAtomItem> doInBackground(String... urls) {
			try {
				// Create RSS reader
				RssAtomReader rssReader = new RssAtomReader(urls[0]);
				rssReader.setProgressBar(progressBar); // Set the progress bar to show real progress
				
				// Parse RSS, get items
				return rssReader.getItems();
			
			} catch (Exception e) {
				Log.e("ITCRssReader", e.getMessage());
			}
			
			return null;
		}
		
		@Override
		protected void onPostExecute(List<RssAtomItem> results) {
			// When the download is done the main activity needs to be started
			Intent i = new Intent(SplashActivity.this, ListPostsActivity.class);
			
			// You might find this not right to use a static attribute to pass data between the 
			// the activities in the application. We tried to pass the List of the RssAtomItem 
			// object with no luck although RssAtomItem implemented Serializable interface.
			// ListPostActivity read null values. So this is the engineer's solution. It works :)
			RssResults.setResults(results); //We need to set the results of the download process
			
			// Show 100% progress
			progressBar.setProgress(100);
			
			// Start new activity and finish this splash activity
			SplashActivity.this.startActivity(i);
			SplashActivity.this.finish();
			
		}
		
	}
}

As you can see we start parsing the RSS ATOM feed in the doInBackground method and when this process ends the onPostExecute method is called. In this method we start the ListPostsActivity.
There is one thing to mention here. We wanted to exchange the List of RssAtomItem objects between the SplashActivity and the ListPostsActivity. Despite of the fact that RssAtomItem class implemented Serializable interface it didn’t work, so we have implemented a little hack here. We exchange data read from the feed by using a static rssResults list which is an attribute of the RssResults class.

RssResults.java

package com.itcuties.app.reader.data;

import java.util.List;

/**
 * This is the class that we use to send data between the SplashActivtiy
 * and the ListPostsActivity.
 * You might find this not right to use a static attribute to pass data between the 
 * the activities in the application. We tried to pass the List of the RssAtomItem 
 * object with no luck although RssAtomItem implemented Serializable interface.
 * ListPostActivity read null values. So this is the engineer's solution. It works :)
 * 
 * @author ITCuties
 */
public class RssResults {

	private static List<RssAtomItem> rssResults;

	public static List<RssAtomItem> getResults() {
		return rssResults;
	}

	public static void setResults(List<RssAtomItem> results) {
		rssResults = results;
	}
	
}

That’s it. This is how our application is implemented :)

Google Play Icon

Install this application from here.

Download icon

Download this sample code here.

Download icon

This code is available on our GitHub repository as well.

29 Responses to "Story of coding ITCutiesApp for Android #1 – parse ATOM, modify HTML input, custom ListView, splash screen, load data in background"

  1. Janice says:

    There’s an error in the package,. “The type java.lang.String cannot be resolved. It is indirectly referenced from required .class files – Configure Build Path”

    Reply
  2. Janice says:

    When are you gonna post the updated version of Multicategory Rss Feed? with the Publish Date.

    Reply
  3. Cheska says:

    This is not working on the emulator.

    Reply
    • itcuties says:

      On what version of the Android are you running this app? 2.2+? What is the error in the catlog?

      Reply
      • Cheska says:

        Yeah Im running this in 4.2. and here is the logcat error.

        08-15 06:22:13.641: E/AndroidRuntime(909): FATAL EXCEPTION: AsyncTask #1
        08-15 06:22:13.641: E/AndroidRuntime(909): java.lang.RuntimeException: An error occured while executing doInBackground()
        08-15 06:22:13.641: E/AndroidRuntime(909): at android.os.AsyncTask$3.done(AsyncTask.java:299)
        08-15 06:22:13.641: E/AndroidRuntime(909): at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
        08-15 06:22:13.641: E/AndroidRuntime(909): at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
        08-15 06:22:13.641: E/AndroidRuntime(909): at java.util.concurrent.FutureTask.run(FutureTask.java:239)
        08-15 06:22:13.641: E/AndroidRuntime(909): at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
        08-15 06:22:13.641: E/AndroidRuntime(909): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
        08-15 06:22:13.641: E/AndroidRuntime(909): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
        08-15 06:22:13.641: E/AndroidRuntime(909): at java.lang.Thread.run(Thread.java:856)
        08-15 06:22:13.641: E/AndroidRuntime(909): Caused by: java.lang.NoClassDefFoundError: org.jsoup.Jsoup
        08-15 06:22:13.641: E/AndroidRuntime(909): at com.itcuties.app.util.text.TextConverter.clearStylesAndJS(TextConverter.java:120)
        08-15 06:22:13.641: E/AndroidRuntime(909): at com.itcuties.app.util.atom.RssAtomParseHandler.endElement(RssAtomParseHandler.java:104)
        08-15 06:22:13.641: E/AndroidRuntime(909): at org.apache.harmony.xml.ExpatParser.endElement(ExpatParser.java:156)
        08-15 06:22:13.641: E/AndroidRuntime(909): at org.apache.harmony.xml.ExpatParser.appendBytes(Native Method)
        08-15 06:22:13.641: E/AndroidRuntime(909): at org.apache.harmony.xml.ExpatParser.parseFragment(ExpatParser.java:513)
        08-15 06:22:13.641: E/AndroidRuntime(909): at org.apache.harmony.xml.ExpatParser.parseDocument(ExpatParser.java:474)
        08-15 06:22:13.641: E/AndroidRuntime(909): at org.apache.harmony.xml.ExpatReader.parse(ExpatReader.java:321)
        08-15 06:22:13.641: E/AndroidRuntime(909): at org.apache.harmony.xml.ExpatReader.parse(ExpatReader.java:294)
        08-15 06:22:13.641: E/AndroidRuntime(909): at javax.xml.parsers.SAXParser.parse(SAXParser.java:390)
        08-15 06:22:13.641: E/AndroidRuntime(909): at javax.xml.parsers.SAXParser.parse(SAXParser.java:266)
        08-15 06:22:13.641: E/AndroidRuntime(909): at com.itcuties.app.util.atom.RssAtomReader.getItems(RssAtomReader.java:54)
        08-15 06:22:13.641: E/AndroidRuntime(909): at com.itcuties.app.SplashActivity$GetRSSDataTask.doInBackground(SplashActivity.java:56)
        08-15 06:22:13.641: E/AndroidRuntime(909): at com.itcuties.app.SplashActivity$GetRSSDataTask.doInBackground(SplashActivity.java:1)
        08-15 06:22:13.641: E/AndroidRuntime(909): at android.os.AsyncTask$2.call(AsyncTask.java:287)
        08-15 06:22:13.641: E/AndroidRuntime(909): at java.util.concurrent.FutureTask.run(FutureTask.java:234)
        08-15 06:22:13.641: E/AndroidRuntime(909): … 4 more

        Reply
  4. Janice says:

    Hello ITCUTIES, I really appreciate this, but I tried and its not working in my code. I was using the Multicategory rss reader. I think the problem is the ATOM FEED URL. Im USING RSS xml URL. Is there’s a difference?

    Reply
  5. Janice says:

    Here’s the erro I got.

    08-13 09:00:51.692: E/AndroidRuntime(3437): FATAL EXCEPTION: AsyncTask #1
    08-13 09:00:51.692: E/AndroidRuntime(3437): java.lang.RuntimeException: An error occured while executing doInBackground()
    08-13 09:00:51.692: E/AndroidRuntime(3437): at android.os.AsyncTask$3.done(AsyncTask.java:299)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at java.util.concurrent.FutureTask.run(FutureTask.java:239)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at java.lang.Thread.run(Thread.java:856)
    08-13 09:00:51.692: E/AndroidRuntime(3437): Caused by: java.lang.NoClassDefFoundError: org.jsoup.Jsoup
    08-13 09:00:51.692: E/AndroidRuntime(3437): at com.itcuties.app.util.text.TextConverter.clearStylesAndJS(TextConverter.java:120)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at com.itcuties.app.util.atom.RssAtomParseHandler.endElement(RssAtomParseHandler.java:104)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at org.apache.harmony.xml.ExpatParser.endElement(ExpatParser.java:156)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at org.apache.harmony.xml.ExpatParser.appendBytes(Native Method)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at org.apache.harmony.xml.ExpatParser.parseFragment(ExpatParser.java:513)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at org.apache.harmony.xml.ExpatParser.parseDocument(ExpatParser.java:474)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at org.apache.harmony.xml.ExpatReader.parse(ExpatReader.java:321)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at org.apache.harmony.xml.ExpatReader.parse(ExpatReader.java:294)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at javax.xml.parsers.SAXParser.parse(SAXParser.java:390)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at javax.xml.parsers.SAXParser.parse(SAXParser.java:266)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at com.itcuties.app.util.atom.RssAtomReader.getItems(RssAtomReader.java:54)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at com.itcuties.app.SplashActivity$GetRSSDataTask.doInBackground(SplashActivity.java:56)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at com.itcuties.app.SplashActivity$GetRSSDataTask.doInBackground(SplashActivity.java:1)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at android.os.AsyncTask$2.call(AsyncTask.java:287)
    08-13 09:00:51.692: E/AndroidRuntime(3437): at java.util.concurrent.FutureTask.run(FutureTask.java:234)
    08-13 09:00:51.692: E/AndroidRuntime(3437): … 4 more
    08-13 09:24:42.702: E/Trace(3865): error opening trace file: No such file or directory (2)

    Reply
  6. andik says:

    why the Splash, Details, Listpost Activities can’t be in one Activity?

    Reply
  7. Mario says:

    Hello,
    why when change address for rss: http://it.notizie.yahoo.com/rss/ and add in category list

    // Mapping initialization
    mapping.put("italia", R.drawable.ic_android);
    

    app start but not showing nothing?
    Where wrong?

    Thanks.

    Reply
  8. sunil says:

    there was problem parsing the package.. please help me

    Reply
  9. macvg says:

    Hi! You have made an excellent work here! I

    But how can i retrieve rss feeds from multiple sources and pass them to different activities?
    I looked at previous tutorials to combine them but until now i am getting only errors…

    Thanks in advance!

    Reply
  10. shekhar says:

    hai itcuties ,
    the above code is working.But I need to download the image from rss feed and upload to listview.i am not getting that url of image by parsing can you please help me. I already post my code in itcuties answers.
    Thanks in advance

    Reply
  11. shekhar says:

    hai again,
    the above code is working for all atom rss feeds but in that no images are coming from rss feed.how should I get imgaes from rss feed.

    Reply
  12. ME_sato says:

    Hi IT Cutie,
    I just want to ask. I use the code from here for buld RSS Reader app (not ATOM) with customizing the code because you say ATOM parse basically from RSS parser. So i just delete “atom” word in code. I launch it and there’s no problem on AndroidRuntime Logcat but after splash screen, the app does’nt show anything. Could you please help me? Oh, and I’m Indonesian, sorry if my english bad.

    Reply
  13. Paolo Francesco says:

    Thanks for this example! Very interesting!
    Only a question: do you know how i can notify the presence of a new post? I’ve an idea based on polling, but i can’t implement it..
    Thanks in advance and sorry for my english! :)

    Reply
  14. Deepa says:

    Hi I need to download the image from rss feed and upload to listview.i am not getting that url of image by parsing,Please help me.

    Reply
  15. Anurag Shrivastava says:

    Hello ITCUTIES,
    you did awesome work and i really appreciate your work. I used this code for my website and modify as according to demands,
    but i don’t understand when it gave error on launch the app if the internet connection is slow or not connected. I used if …else loop in my splash activity but it is not working.
    it shows dialog box “isn’t responding”. can you please help me to solve this problem.

    Thank You,
    Anurag Shrivastava

    Reply
  16. Yatin Kode says:

    What shall I do to show the images in the list as per the posts. The first image of each post should be shown in the list of the feed. Thankyou in advance

    Reply
  17. Anurag Shrivastava says:
    public class ListPostsActivity extends ListActivity {
    
    	// app view
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    
    		// the ATOM feed
    		setListAdapter(new ListAdapter(this, RssResults.getResults()));
    
    	}
    
    	protected void onListItemClick(ListView l, View v, int position, long id) {
    		// When an item is clicked a Details activity needs to be started.
    		Intent i = new Intent(ListPostsActivity.this, DetailsActivity.class);
    
    		// Set data for the details activity to display
    		i.putExtra("title",
    				((RssAtomItem) getListAdapter().getItem(position)).getTitle());
    		i.putExtra("content",
    				((RssAtomItem) getListAdapter().getItem(position)).getContent());
    		i.putExtra("publishDate",
    				((RssAtomItem) getListAdapter().getItem(position))
    						.getPublishDate());
    		i.putExtra("category", ((RssAtomItem) getListAdapter()
    				.getItem(position)).getCategory());
    
    		this.startActivity(i);
    	}
    }
    

    this codes gives an error in ” setListAdapter(new ListAdapter(this, RssResults.getResults())); ” please solve the problem

    Reply

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>