How to parse remote XML using SAX parser with Android AsyncTask

7 September 2012 By Nithya Vasudevan 12,520 views 19 Comments
12 Flares Twitter 1 Facebook 3 Google+ 8 12 Flares ×

Project Description

  • In the previous Android example on SAX parser, we stored the XML file in project’s assets folder and opened the file as InputStream using AssetManager.
  • In this example, we store the XML file in remote server and use android.os.AsyncTask to download and parse the XML and display the result in ListView.

    Applications targeting the Honeycomb (Android 3) SDK or higher should perform networking IO operations using AsyncTask and not in main event loop threads. Trying to do in main thread causes android.os.NetworkOnMainThreadException.

  • When the list item is clicked a Toast message is displayed with description parsed from its XML element.

To parse an XML in Android, you can use DOM and SAX parser API provided by Java platform. In addition to these two parsers, Android provides XmlPullParser which is similar to StAX parser and also the recommended parser to be used in Android.

Environment Used

Prerequisites

Create Android Project

  • Create a new Android Project and name it as “SAXParserAsyncTask“.
  • Enter the package name as “com.theopentutorials.android.activities“.
  • Enter the Activity name as “SAXParserAsyncTaskActivity“.
  • Click Finish.

Create XML file

Create a new XML file and name it as “laptops.xml” and copy the following. Place this file in a remote server, for example Apache server’s “www” folder or <TOMCAT_HOME>/webapps/ROOT folder. Here we used tomcat server.

<?xml version="1.0" encoding="UTF-8"?>
<laptops>
	<laptop model = "Dell Inspiron i13z-3181PNK">
		<brand>Dell</brand>
		<price>$699.99</price>
		<description>Switch your lid to match your mood. 
		    A 13.3" laptop with 3rd Gen Intel Core processor power and optional SWITCH lids 
		</description>		
		<technical-details>
    		Intel 2nd gen Core i3-2367 1.40GHz 1.40 GHz (6MB Cache)
    		6 GB DIMM
    		500GB 5400 rpm SATA Hard Drive
    		13-Inch Screen
    		Windows 7 Home Premium 64-bit
   		</technical-details>
   		<image-url>http://10.0.2.2:8080/laptop-images/Dell-3181PNK.jpg</image-url>
	</laptop>
	<laptop model = "Dell XPS XPS13-9001sLV">
		<brand>Dell</brand>
		<price>$1,399.99</price>
		<description>Strikingly thin, with more room to view. 
		</description>		
		<technical-details>
    		Intel Core i7-2637M (1.70GHz, 4MB Cache)
    		4 GB DIMM
    		256GB Solid State Drive
    		13-Inch Screen
    		Windows 7 Home Premium 64-bit
   		</technical-details>
   		<image-url>http://10.0.2.2:8080/laptop-images/Dell-XPS13.jpg</image-url>
	</laptop>
	
</laptops>

This XML file contains laptops details which also includes laptop image URL which can be downloaded into android.graphics.Bitmap object.

strings.xml

Open res/values/strings.xml and replace it with following content.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, SAXParserAsyncTaskActivity!</string>
    <string name="app_name">SAXParserAsyncTask</string>
	<string name="button">Parse XML using SAX</string>
	<string name="image">Employee Photo</string>
</resources>

XML layout files

This Android application uses two layout files; one for displaying the main layout containing the ListView to display the result of SAX parser and other for defining the row layout for ListView item.

main.xml

Open main.xml file in res/layout and copy the following content.

<?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="fill_parent"
    android:orientation="vertical" >

     <Button
        android:id="@+id/button"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/button" />
     
     <ListView
        android:id="@+id/laptopList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
       />
</LinearLayout>

list_item.xml

Create a new layout file list_item.xml in res/layout and copy the following content.

<?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="wrap_content"
    android:orientation="horizontal"
    android:padding="10dp" >

    <ImageView
        android:id="@+id/thumbnail"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:contentDescription="@string/image" />

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

        <TextView
            android:id="@+id/brand"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#CC0033"
            android:textSize="16sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/model"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#3399FF"
            android:textSize="14sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:textStyle="italic"
            android:typeface="sans" />
    </LinearLayout>

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center_vertical"
        android:orientation="vertical" >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right"
            android:contentDescription="@string/image"
            android:src="@drawable/accessory_indicator" />
    </LinearLayout>

</LinearLayout>

This ListView row layout contains ImageView for displaying laptop image, 3 TextView for displaying brand, model and price of laptop and right arrow icon on each ListView row.

Create Bean classes

Laptop.java

Create a new Java class “Laptop.java” in package “com.theopentutorials.android.beans

package com.theopentutorials.android.beans;

import android.graphics.Bitmap;

public class Laptop {
	private String brand;
	private String model;
	private String description;
	private String techDetails;
	private String price;
	private String imageURL;
	private Bitmap imageBitmap;
	
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	public String getModel() {
		return model;
	}
	public void setModel(String model) {
		this.model = model;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	public String getTechDetails() {
		return techDetails;
	}
	public void setTechDetails(String techDetails) {
		this.techDetails = techDetails;
	}
	public String getPrice() {
		return price;
	}
	public void setPrice(String price) {
		this.price = price;
	}
	public String getImageURL() {
		return imageURL;
	}
	public void setImageURL(String imageURL) {
		this.imageURL = imageURL;
	}
	public Bitmap getImageBitmap() {
		return imageBitmap;
	}
	public void setImageBitmap(Bitmap imageBitmap) {
		this.imageBitmap = imageBitmap;
	}
}

This class has Bitmap object to store laptop image downloaded from URL defined in XML.

Create SAXXMLHandler class

Create a new Java class “SAXXMLHandler.java” in package “com.theopentutorials.android.xml.sax” and copy the following code.

package com.theopentutorials.android.xml.sax;

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 com.theopentutorials.android.beans.Laptop;

public class SAXXMLHandler extends DefaultHandler {
	private List<Laptop> laptops;
	private String tempVal;
	// to maintain context
	private Laptop laptop;

	public SAXXMLHandler() {
		laptops = new ArrayList<Laptop>();
	}

	public List<Laptop> getLaptops() {
		return laptops;
	}

	// Event Handlers
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		// reset
		tempVal = "";
		if (qName.equalsIgnoreCase("laptop")) {
			// create a new instance of Laptop
			laptop = new Laptop();
			laptop.setModel(attributes.getValue("model"));
		}
	}

	public void characters(char[] ch, int start, int length)
			throws SAXException {
		tempVal = new String(ch, start, length);
	}

	public void endElement(String uri, String localName, String qName)
			throws SAXException {

		if (qName.equalsIgnoreCase("laptop")) {
			// add it to the list
			laptops.add(laptop);
		} else if (qName.equalsIgnoreCase("brand")) {
			laptop.setBrand(tempVal);
		} else if (qName.equalsIgnoreCase("description")) {
			laptop.setDescription(tempVal);
		} else if (qName.equalsIgnoreCase("technical-details")) {
			laptop.setTechDetails(tempVal);
		} else if (qName.equalsIgnoreCase("image-url")) {
			laptop.setImageURL(tempVal);
		} else if (qName.equalsIgnoreCase("price")) {
			laptop.setPrice(tempVal);
		}
	}
}

SAX (Simple API for XML) is an event-based sequential access parser API with number of callback methods that will be called when events occur during parsing. This class parses a XML file containing laptop details and stores in a list as an laptop object.

Create SAXXMLParser class

Create a new Java class “SAXXMLParser.java” in package “com.theopentutorials.android.xml.sax” and copy the following code.

package com.theopentutorials.android.xml.sax;

import java.io.InputStream;
import java.util.List;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import com.theopentutorials.android.beans.Laptop;
import android.util.Log;

public class SAXXMLParser {

	public static List<Laptop> parse(InputStream is) {
		List<Laptop> laptops = null;
		try {
			// create a XMLReader from SAXParser
			XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser()
					.getXMLReader();
			// create a SAXXMLHandler
			SAXXMLHandler saxHandler = new SAXXMLHandler();
			// store handler in XMLReader
			xmlReader.setContentHandler(saxHandler);
			// the process starts
			xmlReader.parse(new InputSource(is));
			// get the `Laptop list`
			laptops = saxHandler.getLaptops();

		} catch (Exception ex) {
			Log.d("XML", "SAXXMLParser: parse() failed");
			ex.printStackTrace();
		}

		// return Laptop list
		return laptops;
	}
}

Create Custom ArrayAdapter class for ListView

Create a new Java class “CustomListViewAdapter.java” in package “com.theopentutorials.android.adapters” and copy the following code.

package com.theopentutorials.android.adapters;

import java.util.List;
import com.theopentutorials.android.activities.R;
import com.theopentutorials.android.beans.Laptop;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class CustomListViewAdapter extends ArrayAdapter<Laptop> {
	Activity context;
	List<Laptop> laptops;

	public CustomListViewAdapter(Activity context, List<Laptop> laptops) {
		super(context, R.layout.list_item, laptops);
		this.context = context;
		this.laptops = laptops;
	}

	/*private view holder class*/
	private class ViewHolder {
		ImageView imageView;
		TextView txtBrand;
		TextView txtModel;
		TextView txtPrice;
	}
	
	public Laptop getItem(int position) {
		return laptops.get(position);
	}
	
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder;
		LayoutInflater inflater = context.getLayoutInflater();

		if (convertView == null) {
			convertView = inflater.inflate(R.layout.list_item, null);
			holder = new ViewHolder();
			holder.txtBrand = (TextView) convertView.findViewById(R.id.brand);
			holder.txtModel = (TextView) convertView.findViewById(R.id.model);
			holder.txtPrice = (TextView) convertView.findViewById(R.id.price);
			holder.imageView = (ImageView) convertView.findViewById(R.id.thumbnail);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}

		Laptop laptop = (Laptop) getItem(position);

		holder.txtBrand.setText(laptop.getBrand());
		holder.txtModel.setText(laptop.getModel());
		holder.imageView.setImageBitmap(laptop.getImageBitmap());
		holder.txtPrice.setText(laptop.getPrice() + "");

		return convertView;
	}
}

/* USING BaseAdapter */
/*public class CustomListViewAdapter extends BaseAdapter {

	Activity context;
	List<Laptop> laptops;

	public CustomListViewAdapter(Activity context, List<Laptop> laptops) {
		super();
		this.context = context;
		this.laptops = laptops;
	}

	private class ViewHolder {
		ImageView imageView;
		TextView txtBrand;
		TextView txtModel;
		TextView txtPrice;
	}
	
	public int getCount() {
		return laptops.size();
	}

	public Object getItem(int position) {
		return laptops.get(position);
	}

	public long getItemId(int position) {
		return 0;//employees.indexOf(getItem(position));
	}

	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder;
		LayoutInflater inflater = context.getLayoutInflater();

		if (convertView == null) {
			convertView = inflater.inflate(R.layout.list_item, null);
			holder = new ViewHolder();
			holder.txtBrand = (TextView) convertView.findViewById(R.id.brand);
			holder.txtModel = (TextView) convertView.findViewById(R.id.model);
			holder.txtPrice = (TextView) convertView.findViewById(R.id.price);
			holder.imageView = (ImageView) convertView.findViewById(R.id.thumbnail);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}

		Laptop laptop = (Laptop) getItem(position);

		holder.txtBrand.setText(laptop.getBrand());
		holder.txtModel.setText(laptop.getModel());
		holder.imageView.setImageBitmap(laptop.getImageBitmap());
		holder.txtPrice.setText(laptop.getPrice() + "");

		return convertView;
	}
}*/

Commented class uses custom BaseAdapter for ListView.

Create SAXParserAsyncTaskActivity class

package com.theopentutorials.android.activities;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import com.theopentutorials.android.adapters.CustomListViewAdapter;
import com.theopentutorials.android.beans.Laptop;
import com.theopentutorials.android.xml.sax.SAXXMLParser;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListView;

public class SAXParserAsyncTaskActivity extends Activity implements
		OnClickListener, OnItemClickListener {
	Button button;
	ListView listView;
	List<Laptop> laptops;
	CustomListViewAdapter listViewAdapter;
	
	static final String URL = "http://10.0.2.2:8080/laptops.xml";
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        findViewsById();
		button.setOnClickListener(this);
		listView.setOnItemClickListener(this);
    }
    
    private void findViewsById() {
		button = (Button) findViewById(R.id.button);
		listView = (ListView) findViewById(R.id.laptopList);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position,
		long id) {
    }
    
	@Override
	public void onClick(View view) {
		GetXMLTask task = new GetXMLTask(this);
		task.execute(new String[] { URL });		
	}
	
	//private inner class extending AsyncTask
	private class GetXMLTask extends AsyncTask<String, Void, List<Laptop>> {
		private Activity context;
		public GetXMLTask(Activity context) {
			this.context = context;
		}
		
		@Override
		protected void onPostExecute(List<Laptop> laptops) {
			listViewAdapter = new CustomListViewAdapter(context, laptops);  
			listView.setAdapter(listViewAdapter);
		}

		/* uses HttpURLConnection to make Http request from Android to download
		 the XML file */
		private String getXmlFromUrl(String urlString) {
			StringBuffer output = new StringBuffer("");
			try {
				InputStream stream = null;
				URL url = new URL(urlString);
				URLConnection connection = url.openConnection();

				HttpURLConnection httpConnection = (HttpURLConnection) connection;
				httpConnection.setRequestMethod("GET");
				httpConnection.connect();

				if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
					stream = httpConnection.getInputStream();

					BufferedReader buffer = new BufferedReader(
							new InputStreamReader(stream));
					String s = "";
					while ((s = buffer.readLine()) != null)
						output.append(s);
				}
				
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			return output.toString();
			
			/* ---Using Apache DefaultHttpClient for applications targeting 
			 Froyo and previous versions --- */
			/*String xml = null;

			try {
				DefaultHttpClient httpClient = new DefaultHttpClient();
				HttpGet httpGet = new HttpGet(url);

				HttpResponse httpResponse = httpClient.execute(httpGet);
				HttpEntity httpEntity = httpResponse.getEntity();
				xml = EntityUtils.toString(httpEntity);

			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			} catch (ClientProtocolException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return xml;*/
		}  

		@Override
		protected List<Laptop> doInBackground(String... urls) {
			List<Laptop> laptops = null;
			String xml = null;
			for (String url : urls) {
				xml = getXmlFromUrl(url);

				InputStream stream = new ByteArrayInputStream(xml.getBytes());
				laptops = SAXXMLParser.parse(stream);

				for (Laptop laptop : laptops) {
					String imageURL = laptop.getImageURL();
					Bitmap bitmap = null;
					BitmapFactory.Options bmOptions = new BitmapFactory.Options();
					bmOptions.inSampleSize = 1;

					try {
						bitmap = BitmapFactory
								.decodeStream(new 
								URL(imageURL).openStream(),
								null, bmOptions);
					} catch (MalformedURLException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					}
					laptop.setImageBitmap(bitmap);
				}
			}
			// stream.close();
			return laptops;
		}
	}
}

The above code works as follows;

  • Whenever we need to perform lengthy operation or any background operation we can use Asyntask which executes a task in background and publish results on the UI thread without having to manipulate threads and/or handlers.
  • In Button click event, we create and execute the task. This invokes doInBackground() where we open a Http URL connection and download the XML file, parse it using SAX and return list of laptops.
  • Once the background computation finishes, onPostExecute() is invoked on the UI thread which displays the result in ListView using custom ArrayAdapter or BaseAdapter.

We are running the remote server in the local machine itself (localhost). So you may try to access the resources using http://localhost:8080, but you may get “Connection refused” (failed to connect to localhost/127.0.0.1) exception because our code is running in an emulator so localhost refers to the emulator itself and not the machine which is running the Tomcat/Apache server. So to access local machine from emulator, you have to use 10.0.2.2 IP address which points to the machine which has the running emulator.

Android includes two HTTP clients: HttpURLConnection and Apache HTTP Client.
For Gingerbread and later, HttpURLConnection is the best choice.

AndroidManifest.xml

Define the activity in AndroidManifest.xml file. To access internet from Android application set the android.permission.INTERNET permission in manifest file as shown below.

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

    <uses-sdk android:minSdkVersion="15" />
 <uses-permission android:name="android.permission.INTERNET" >
    </uses-permission>
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".SAXParserAsyncTaskActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Output

Run your application

Project Folder Structure

The complete folder structure of this example is shown below.

Tags: , , , , , , , , , , , , , , , , , , ,

  • Rathish

    Hi,
    Awesome tutorial.. Can you give similar example for json parsing…
    Thanx in advaance…

    • Mahalakshmi

      Thank you , good tutorial .it helps me lot ..

  • http://www.guillaumedesbieys.com guillaume

    Hi,
    Thank you, it’s a great tutorial.
    But in my case I’ve to handle different XML file.
    So I want to know how you handle it, in a conception way.
    Because if I have different XML file, I need to have different SAXXMLParser, SAXXMLHandler,…
    It’s the good way to have different SAXXMLParser and SAXXMLHandler ?
    Or there is a way to have one SAXXMLParser and one SAXXMLHandler ?

    Thanks

    • http://theopentutorials.com Praveen Macherla

      SAXXMLParser is just a helper class. It is going to be the same. But you have to create a SAXXMLHandler which suits your XML.

  • http://www.guillaumedesbieys.com guillaume

    But SAXXMLParser is calling SAXXMLHandler, so if I have differents SAXXMLHandler I need different SAXXMLParser to call the good SAXXMLHandler ?

    • http://theopentutorials.com Praveen Macherla

      Yes.

  • K.Navin

    Excellent tutorial Please provide one for socket programming (WiFi)peer to peer
    between android devices /emulator clear steps seting adb etc..

  • huy hai Tran

    can you give me full source code ? thank’s

  • Richard

    Ms. Vasudevan, I am working on a small project/app that uses sax parser to pull data from yahoo search API. I have created the skeleton and followed your tutorial on this subject but I am having problems finishing it and getting it to work correctly. I was wondering if you do freelance work. Would you be interested on helping me?

  • giammario

    Hi, Thank you, it’s a great tutorial.
    but when I click the list do not have the technical-details.
    Is this normal? where am I wrong?
    thanks

    • http://theopentutorials.com Nithya Vasudevan

      Inside onItemClick() method of SAXParserAsyncTaskActivity class, you have to write your code to display technical details.

  • Prakash

    Hi Sister,
    Great tutorials. Thanks a lot for sharing the knowledge. I have used this method parse a file. But it does not work. Could you help me.
    Prakash

  • Robin

    I am testing on my kindle not an emulator and I get as far as clicking the button and it stops working. Any ideas?

  • viral

    Hi ! your tutorial is the best. but i cant understand the following code.

    listViewAdapter = new CustomListViewAdapter(context, laptops);
    listView.setAdapter(listViewAdapter);

    Query 1- what happen in above CustomListViewAdapter constructor where u put laptops it is obj of List laptops. ok . the List obj it’s store the reference variable of Laptop class or Actual object value(i.e new Laptop())Laptop=new Laptop();

    Query 2- public Laptop getItem(int position) {
    return laptops.get(position);
    }
    Laptop laptop = (Laptop) getItem(position);// line 1

    holder.txtBrand.setText(laptop.getBrand());
    holder.txtModel.setText(laptop.getModel());
    holder.imageView.setImageBitmap(laptop.getImageBitmap());
    holder.txtPrice.setText(laptop.getPrice() + “”);

    // I think in line 1 getItem method return obj of Laptop class i.e laptop ok. i am right. so what happen laptops.get(0 ) return laptop obj of class employee again it return when method called getitem laptops.get(1) return obj of Laptop class how can we achive the multiple string value from bean class using that same obj laptop initialize once and store value odf string in bean class once still we get multiple string value from bean class using get method . how its possible. thanks. reply me.

  • Joy King

    Hi, Thank you, it is a great tutorial. I think this will take care of what I need to do. It seems to do exactly as app will.

  • sam2208

    I tried to execute this example and i use Apache server’s , but it gives error in log cat
    the error is in image attachment.
    help me if you have any solution,
    Thanks in advance!

    • The Open Tutorials

      Your laptops object is null. Probably you didn’t have any data.

  • Francesco

    hello,

    thanks for the tutorial can you give an example of how if I click it opens the detail with the same data

  • Pingback: Android XML parsing to SQlite