Android Lists V: Accessing and Consuming a SOAP Web Service II

Continued from: Android Lists IV: Accessing and Consuming a SOAP Web Service I

When we left off, we were able to see the result XML that was retrieved from the SOAP web service that was called with the StockQuoteFetcher.  Now it’s time to parse that XML data into Java objects, so that the data can be used within the StockViewer application.

The result of the web service that was chosen in the previous article actually just returns a String of XML, like this:

<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://www.webserviceX.NET/">
	&lt;StockQuotes&gt;
		&lt;Stock&gt;
			&lt;Symbol&gt;MSFT&lt;/Symbol&gt;
			&lt;Last&gt;25.92&lt;/Last&gt;
			&lt;Date&gt;4/29/2011&lt;/Date&gt;
			&lt;Time&gt;4:00pm&lt;/Time&gt;
			&lt;Change&gt;-0.79&lt;/Change&gt;
			&lt;Open&gt;26.55&lt;/Open&gt;
			&lt;High&gt;26.64&lt;/High&gt;
			&lt;Low&gt;25.36&lt;/Low&gt;
			&lt;Volume&gt;319317856&lt;/Volume&gt;
			&lt;MktCap&gt;217.8B&lt;/MktCap&gt;
			&lt;PreviousClose&gt;26.71&lt;/PreviousClose&gt;
			&lt;PercentageChange&gt;-2.96%&lt;/PercentageChange&gt;
			&lt;AnnRange&gt;22.73-31.08&lt;/AnnRange&gt;
			&lt;Earns&gt;2.343&lt;/Earns&gt;
			&lt;P-E&gt;11.40&lt;/P-E&gt;
			&lt;Name&gt;Microsoft Corpora&lt;/Name&gt;
		&lt;/Stock&gt;
	&lt;/StockQuotes&gt;
</string>

This makes more conventional methods of retrieving a structured result a little more difficult since the result is just an XML string as opposed to an XML result that is structured.  That means that the XML data will need to be pulled from the web service response, and then that XML data will need to be parsed to create the Java objects that can be used in the Stock Viewer application.

XML Parsing Options

One of the many nice features of Android is that there is more than one way to go about parsing an XML document.

SAX Parser

The SAX parser is entirely the same in Android as it is in Java, which is cool because if you have a parser for a Java application, you can use it directly in an Android application.

Advantages
  • It’s quick.  SAX is event based and traverses the XML document from start to end
  • Small memory footprint
Disadvantages
  • Somewhat tedious to develop and maintain
  • Restricted to only traversing the XML document from start to finish a single time
  • Intended for forward, read-only operations

DOM Parser

Advantages
  • Ability to add/remove/edit elements of the XML document via the Document Object Model
  • Visit single elements multiple times without having to re-parse the document
  • Ability to access elements in an arbitrary sequence
Disadvantages
  • Entire document is loaded into memory
  • Slowest parsing method of all three

Pull Parser

Advantages
  • Ability to “pull” data from the XML document, as opposed to being pushed the data (via callbacks) when using SAX
  • Small memory footprint
Disadvantages
  • Slower than SAX
  • Restricted to only traversing the XML document from start to finish a single time
  • Intended for forward, read-only operations

Parsing the SOAP Web Service Response

To make it a bit clearer, the XML that is extracted from the web service result looks something like this (a little bit easier to read than the encoded < and > symbols up above):

<StockQuotes>
 <Stock>
 <Symbol>MSFT</Symbol>
 <Last>25.92</Last>
 <Date>4/29/2011</Date>
 <Time>4:00pm</Time>
 <Change>-0.79</Change>
 <Open>26.55</Open>
 <High>26.64</High>
 <Low>25.36</Low>
 <Volume>319317856</Volume>
 <MktCap>217.8B</MktCap>
 <PreviousClose>26.71</PreviousClose>
 <PercentageChange>-2.96%</PercentageChange>
 <AnnRange>22.73 - 31.08</AnnRange>
 <Earns>2.343</Earns>
 <P-E>11.40</P-E>
 <Name>Microsoft Corpora</Name>
 </Stock>
</StockQuotes>

For this section, I have opted to go with SAX.  There are two steps that need to be accomplished in order to retrieve a listing of StockQuote from the XML result string.

SAX Handler

First, we need to create a SAX handler, which will be a class that extends the DefaultHandler class that is provided in the SAX namespace.  This handler is essentially the logic for how the data in the XML document is mapped to properties of the StockQuote object.

package com.austinrasmussen.stockviewer;

import java.util.ArrayList;

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

public class StockQuoteHandler extends DefaultHandler {

	private ArrayList<StockQuote> quotes = new ArrayList<StockQuote>();
	private StockQuote currentQuote = new StockQuote();
	private String currentNodeText;

	private final String STOCK = "Stock";
	private final String SYMBOL = "Symbol";
	private final String LAST = "Last";

	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		// Create a new StockQuote for every corresponding
		// <Stock> node in the XML document
		if (localName.equalsIgnoreCase(STOCK)) {
			currentQuote = new StockQuote();
		}
	}

	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		// Retrieve the text content of the current node
		// that is being processed
		currentNodeText = new String(ch, start, length);
	}

	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		if(localName.equalsIgnoreCase(SYMBOL)){
			currentQuote.setTickerSymbol(currentNodeText);
		}else if(localName.equalsIgnoreCase(LAST)){
			currentQuote.setQuote(Double.parseDouble(currentNodeText));
		}else if(localName.equalsIgnoreCase(STOCK)){
			// When the </Stock> element is reached, this quote object is complete.
			quotes.add(currentQuote);
		}
	}

	public ArrayList<StockQuote> getQuotes()
	{
		return quotes;
	}
}

The handler, while having many conditional statements, is quite straightforward how the data is mapped from the XML element names to the properties of the StockQuote object. Here’s a synopsis of how this handler works:

  1. The constants at the top define the names of the XML elements that the Stock Viewer application is interested in.
  2. The startElement(…) method is invoked every time a start element of the XML document is encountered, such as <Stock> or <Symbol>, but not </Stock> or </Symbol>.
  3. The characters(…) method is invoked every time character data inside of the current XML element is encountered, such as MSFT in <Stock>MSFT</Stock>.
  4. The endElement(…) method is invoked every time an end element of the XML document is encountered, such as </Stock> or </Symbol>, but not <Stock> or <Symbol>.
    • Most of the interesting operations happen within this method.
    • This is when the handler knows that all of the data for this particular element can be read in and assigned to a property (since the characters(…) method would have been invoked before this method).
    • This is also when we might possibly encounter the </Stock> element, which means that the current StockQuote object is fully populated.

Using the Handler

Now that the handler is created, all that is left to do is use it to process the web service response XML.  There’s a handy Xml(…) method that Android provides in the android.util namespace that will assist in performing this operation.

Let’s revisit the Fetch() method of StockQuoteFetcher… Right now, it looks something like this:

public String Fetch()
{
	String result = "";
	HttpTransportSE httpRequest = new HttpTransportSE(URL);

	try
    {
		httpRequest.call(SOAP_ACTION, envelope);
		SoapPrimitive response = (SoapPrimitive)envelope.getResponse();
        result =  response.toString();
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }
	return result;
}

With just a little bit of modification and the Xml(…) method, the Fetch() method is able to return a List of StockQuote objects to the Activity class.

public List<StockQuote> Fetch()
{
	HttpTransportSE httpRequest = new HttpTransportSE(URL);

	StockQuoteHandler quoteParser = new StockQuoteHandler();;
	try
    {
		httpRequest.call(SOAP_ACTION, envelope);
		SoapPrimitive response = (SoapPrimitive)envelope.getResponse();
		Xml.parse(response.toString(), quoteParser);
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }

	return quoteParser.getQuotes();
}

Fantastic! That was simple enough.  We can now replace the static data set in the main Activity class with the result of the Fetch(…) method.

package com.austinrasmussen.stockviewer;

import java.util.List;

import android.app.ListActivity;
import android.os.Bundle;

public class StockList extends ListActivity {

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        StockQuoteFetcher sqf = new StockQuoteFetcher("MSFT ORCL AMZN ERTS");
        List<StockQuote> quoteResult = sqf.Fetch();

        setListAdapter(new StockQuoteAdapter(this, quoteResult));
    }
}

Results

Let’s see how the Stock Viewer application works with the newly added SAX handler that has been added to parse the web service response.

Excellent!  Just as expected, the data was parsed into StockQuote objects successfully by the StockQuoteHandler.  The resulting list of StockQuote objects was then used as the data source for the StockQuoteAdapter instead of the static content that had been used previously.

Final Code

package com.austinrasmussen.stockviewer;
 
import java.util.List;
 
import android.app.ListActivity;
import android.os.Bundle;
 
public class StockList extends ListActivity {
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        StockQuoteFetcher sqf = new StockQuoteFetcher("MSFT ORCL AMZN ERTS");
        List<StockQuote> quoteResult = sqf.Fetch();
 
        setListAdapter(new StockQuoteAdapter(this, quoteResult));
    }
}
package com.austinrasmussen.stockviewer;

public class StockQuote {
	private String tickerSymbol;
	private Double quote;

	public StockQuote() {
	}

	public StockQuote(String tickerSymbol, Double quote) {
		this.tickerSymbol = tickerSymbol;
		this.quote = quote;
	}
	
	public void setTickerSymbol(String tickerSymbol) {
		this.tickerSymbol = tickerSymbol;
	}

	public String getTickerSymbol() {
		return tickerSymbol;
	}

	public void setQuote(double quote) {
		this.quote = quote;
	}

	public Double getQuote() {
		return quote;
	}
}
package com.austinrasmussen.stockviewer;
 
import java.util.List;
 
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class StockQuoteAdapter extends ArrayAdapter<StockQuote> {
    private final Activity activity;
    private final List<StockQuote> stocks;
 
    public StockQuoteAdapter(Activity activity, List<StockQuote> objects) {
        super(activity, R.layout.stock_quote_list_item , objects);
        this.activity = activity;
        this.stocks = objects;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View rowView = convertView;
        StockQuoteView sqView = null;
 
        if(rowView == null)
        {
            // Get a new instance of the row layout view
            LayoutInflater inflater = activity.getLayoutInflater();
            rowView = inflater.inflate(R.layout.stock_quote_list_item, null);
 
            // Hold the view objects in an object,
            // so they don't need to be re-fetched
            sqView = new StockQuoteView();
            sqView.ticker = (TextView) rowView.findViewById(R.id.ticker_symbol);
            sqView.quote = (TextView) rowView.findViewById(R.id.ticker_price);
 
            // Cache the view objects in the tag,
            // so they can be re-accessed later
            rowView.setTag(sqView);
        } else {
            sqView = (StockQuoteView) rowView.getTag();
        }
 
        // Transfer the stock data from the data object
        // to the view objects
        StockQuote currentStock = stocks.get(position);
        sqView.ticker.setText(currentStock.getTickerSymbol());
        sqView.quote.setText(currentStock.getQuote().toString());
 
        return rowView;
    }
 
    protected static class StockQuoteView {
        protected TextView ticker;
        protected TextView quote;
    }
}
package com.austinrasmussen.stockviewer;

import java.util.List;

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.PropertyInfo;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapPrimitive;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.AndroidHttpTransport;
import org.ksoap2.transport.HttpTransportSE;

import android.util.Xml;

public class StockQuoteFetcher {
	private final String NAMESPACE = "http://www.webserviceX.NET/";
	private final String METHOD_NAME = "GetQuote";
	private final String SOAP_ACTION = "http://www.webserviceX.NET/GetQuote";
	private final String URL = "http://www.webservicex.net/stockquote.asmx";
	
	private final SoapSerializationEnvelope envelope;
	
	public StockQuoteFetcher(String quotes)
	{
		SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
		
		PropertyInfo quotesProperty = new PropertyInfo();
		quotesProperty.setName("symbol");
		quotesProperty.setValue(quotes);
		quotesProperty.setType(String.class);
		request.addProperty(quotesProperty);
		
		envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
		envelope.dotNet = true;
		envelope.setOutputSoapObject(request);
	}
	
	public List<StockQuote> Fetch()
	{
		HttpTransportSE httpRequest = new HttpTransportSE(URL);
		
		StockQuoteHandler quoteParser = new StockQuoteHandler();;
		try
        {
			httpRequest.call(SOAP_ACTION, envelope);
			SoapPrimitive response = (SoapPrimitive)envelope.getResponse();
			Xml.parse(response.toString(), quoteParser);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        
		return quoteParser.getQuotes();
	}
		
}
package com.austinrasmussen.stockviewer;

import java.util.ArrayList;

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

public class StockQuoteHandler extends DefaultHandler {

	private ArrayList<StockQuote> quotes = new ArrayList<StockQuote>();
	private StockQuote currentQuote = new StockQuote();
	private String currentNodeText;

	private final String STOCK = "Stock";
	private final String SYMBOL = "Symbol";
	private final String LAST = "Last";

	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		// Create a new StockQuote for every corresponding
		// <Stock> node in the XML document
		if (localName.equalsIgnoreCase(STOCK)) {
			currentQuote = new StockQuote();
		}
	}

	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		// Retrieve the text content of the current node 
		// that is being processed
		currentNodeText = new String(ch, start, length);
	}

	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		if(localName.equalsIgnoreCase(SYMBOL)){
			currentQuote.setTickerSymbol(currentNodeText);
		}else if(localName.equalsIgnoreCase(LAST)){
			currentQuote.setQuote(Double.parseDouble(currentNodeText));
		}else if(localName.equalsIgnoreCase(STOCK)){
			// When the </Stock> element is reached, this quote object is complete.
			quotes.add(currentQuote);
		}
	}
	
	public ArrayList<StockQuote> getQuotes()
	{
		return quotes;
	}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
    <ListView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@android:id/list" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/ticker_symbol"
        android:layout_alignParentLeft="true"
        android:textColor="@android:color/white"
        android:textSize="20sp"  
        android:paddingTop="4dp"
        android:paddingBottom="4dp" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:id="@+id/ticker_price"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"  />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="com.austinrasmussen.stockviewer" android:versionCode="1"
	android:versionName="1.0">
	<uses-permission android:name="android.permission.INTERNET"></uses-permission>


	<application android:icon="@drawable/icon" android:label="@string/app_name">
		<activity android:name=".StockList" 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>

Tags: , , , , ,

This entry was posted on Saturday, April 30th, 2011 at 1:07 pm and is filed under Android. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

9 Responses to “Android Lists V: Accessing and Consuming a SOAP Web Service II”

Anton May 23rd, 2012 at 3:03 am

Hi Austin,

I am a newbie to android and have been trying to get this app to run, but I have 3 build problems with the code as you have it:

1. quoteParser.getQuotes() is not defined

2. Xml.parse(response.toString(), quoteParser) fails

3. StockQuote currentStock = stocks.get(position); – can’t convert between object and StockQuote

Do you ever provide the source code?

thanks

Anton (from oz)

Austin Rasmussen May 23rd, 2012 at 6:46 pm

Anton,

I do provide the source, usually inline with the articles, but I understand that sometimes it may be difficult to piece together.

I started setting up a place where I’d publicly publish the finished product, but it appears that I never finished following through.

The completed StockViewer application can be found here: https://github.com/austin-rasmussen/vexed-logic/tree/master/StockViewer

Take a look through this and reply back if you’re still experiencing problems!

adolfofermo May 24th, 2012 at 9:26 am

Hi Austin! I’m getting an error when i run from an existing source your project (downloaded from GitHub). The error is the following:

java.lang.NoClassDefFoundError: org.ksoap2.serialization.SoapObject

I made the project to run with the 2.2 SDK and I’m using a 2.3 Samsung GSII to test it. I added the ksoap2-android-assembly-2.5.4-jar-with-dependencies.jar in the lib folder, that you talked about on the first article. I checked the Manifest and it has the permissions also added the line to run with minimum 8 SDK version.

Thanks in advance for your help! Great article.

Adolfo Fermo May 24th, 2012 at 11:14 am

I found the error, another mischief from Eclipse and Android. The directory to store the ksoap is not /lib, is /libs. Once again thanks for your help, excellent tutorial.

Austin Rasmussen May 24th, 2012 at 6:38 pm

Awesome! Glad to hear you found a solution, Adolfo.

Fred W July 8th, 2012 at 8:18 am

I received a network on main thread error in API 15. That was resolved by wrapping the fetch and setListAdapter calls in an AsyncTask. Thanks for the great tutorial!

mskydt December 16th, 2012 at 8:31 am

Hey m8
Thx for some excellent toturials! Really enjoyed them.

I got one question tho. Im making an application where i want to update the stockquotes every 1 min or so.

I know i need to make the call to the fetch-operation in a ASyncTask/Thread, otherwise it interfers with the main thread.

But my question is, where would be the best place to call the webservice? onCreate is only called once, when the application starts. So im looking for a place that can update the stock quotes continously.

Hiba December 29th, 2012 at 5:14 am

excellent tutorial, I really appreciated.
thanks a lot =)

Pratik March 16th, 2013 at 10:16 am

Hi Austin, a big thank you for such a detailed tutorial and nice explanation… found very interesting..and big thanks to Adolfo too..even i had the same issue and your solution fought bravely.

Leave a Reply

You must be logged in to post a comment.