Quantcast
Channel: Thiranjith's Blog » tutorial
Viewing all articles
Browse latest Browse all 10

Introduction to working with kSOAP2 in Android

$
0
0

This is an introductory tutorial showing how to consume SOAP-based web services in Android using the kSOAP2 library.

How to use kSOAP2 library in Android

Despite rising popularity of RESTful services, there are still a significant number of SOAP-based web services around that are being consumed by mobile applications; specially when working in the enterprise world. Android does not have native support for web service consumption, and this handy light-weight library called kSOAP2 allows Android applications to easily and efficiently consume SOAP-based web services.

In this tutorial I will show you how to use kSOAP2 in Android to consume a publicly available SOAP-based web service. A fully working example of an Android project illustrating the use of kSOAP2 can be found at GitHub.

Introducing the service and operations

I am using a publicly available web service from www.service-repository.com that allows us to view weather information. I will be focusing on two specific operations to highlight the basic features of using kSOAP2:

  1. GetWeatherInformation: A zero-argument operation that return weather information managed by the service (You can have a look at the request and response XML from here)
  2. GetCityForecastByZIP: A single argument operation that returns weather forecast for a city (argument specifies the US Zip code). You can have a look at the request and response XML from here

The service definition can be downloaded/viewed from this link (open it in Notepad or any other text-editor to view the content).

Creating a new Android project with kSOAP2

  1. Create a standard Android project
  2. Download the ksoap2-android-assembly-jar-with-dependencies.jar from kSOAP2 home page
  3. Copy the downloaded jar file to your Android project’s libs folder. This will enable your Android application to automatically pick up this library when it generates the final artefacts (i.e. the APK file)
  4. Add the android.permission.INTERNET permission to your application’s Manifest.xml file to allow your application to talk to web services. Add the following code snippet to your application’s MANIFEST:
    <uses-permission android:name="android.permission.INTERNET" />

NOTE: There are multiple ways in which you can include kSOAP2 in your Android project, and the method I described above is the most simplest. You can read about other options (e.g. in Maven builds or as an Android library etc.) at here.

Invoking a web service request

Invoking a web service with kSOAP2 involves the following steps:

  1. Create a SOAP Envelope using the org.ksoap2.serialization.SoapSerializationEnvelope class.
  2. Set the request information for the SOAP Envelope. The request information will be represented by a org.ksoap2.serialization.SoapObject object. I will show how to create request objects shortly in the next section.
  3. Create an HTTP Transport request object to deliver the SOAP request (org.ksoap2.transport.HttpTransportSE)
  4. Send the SOAP request over HTTP using the HTTP Transport and SOAP Envelope objects created earlier. This call is a synchronous call.
  5. Process the web service response (or handle any unexpected errors). A detailed example of processing a relatively complex web service response can be found later in the article.

The code snippet below summarises the above steps in Android:

SoapObject parameter = getSOAPRequestParameter();
// 1. Create SOAP Envelope 
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
// 2. Set the request parameters
envelope.setOutputSoapObject(parameter);

// 3. Create an HTTP Transport object to send the web service request
HttpTransportSE httpTransport = new HttpTransportSE(WSDL_URL);
httpTransport.debug = true; // allows capture of raw request/respose in Logcat

// 4. Make the web service invocation
httpTransport.call(WS_NAMESPACE + WS_METHOD_NAME, envelope);

// Logging the raw request and response (for debugging purposes)
Log.d(TAG, "HTTP REQUEST:\n" + httpTransport.requestDump);
Log.d(TAG, "HTTP RESPONSE:\n" + httpTransport.responseDump);

// 5. Process the web service response
if (envelope.bodyIn instanceof SoapObject) { // SoapObject = SUCCESS
    SoapObject soapObject = (SoapObject) envelope.bodyIn;
    T processedResponse = parseSOAPResponse(soapObject);
    // ... do whatever you want with this object now
} else if (envelope.bodyIn instanceof SoapFault) { // SoapFault = FAILURE
    SoapFault soapFault = (SoapFault) envelope.bodyIn;
    throw new Exception(soapFault.getMessage());
}

As mentioned above, your SOAP requests are synchronous and depending on network conditions it can take a while before even a simplest of requests can be invoked. This can cause application freezes and crashes. Therefore, it is highly recommended to do both the service invocation and request processing within a background thread in Android.

This can be achieved using an AsyncTask, and putting the above code inside its doInBackground method. You can have a look at how this is incorporated into the solution in my sample project at Github.

Creating a service request

All SOAP requests are created in kSOAP2 using a org.ksoap2.serialization.SoapObject object. You need to specify the web service namespace, and the service method name when constructing a SoapObject. You can find the namespace and method name details in the wsdl file itself, or in the documentation provided by the service provider.

Example SOAP request with no arguments

For example, you can create a zero-argument SOAP request required for the GetWeatherInformation service method as follows:

// see com.example.ksoap2.wsclient.weather.GetWeatherInformationTask class
// for full source code
SoapObject request = new SoapObject("http://ws.cdyne.com/WeatherWS/" /* namespace */, 
                                    "GetWeatherInformation" /* web service method */);
// set the request in the envelope (shown in the previous section)
envelope.setOutputSoapObject(parameter);

Example SOAP request with an argument

Lets have a look at a slightly more complex scenario that involves passing in arguments to a web service request, such as the case with GetCityForecastByZIP service method. As per its service definition, it accepts a single argument that describe the zip code of a city.

The only step different from the above no-argument scenario, is that now we are adding request properties to our SoapObject using PropertyInfo class. org.ksoap2.serialization.PropertyInfo class enables us to add property name-value pairs as illustrated in the code snippet below.

NOTE: Adding complex request objects is out of scope for this article. I will be writing another article soon about that soon.

String zipCode = 12345; // example zip code
// 1. Create the SoapObject 
SoapObject request = new SoapObject("http://ws.cdyne.com/WeatherWS/" /* namespace */, 
                                    "GetCityForecastByZIP" /* web service method */);
// 2. Define the property
PropertyInfo property = new PropertyInfo();
property.setNamespace("http://ws.cdyne.com/WeatherWS/"); // namespace to ensure that the element-name is prefixed with the namespace
property.setName("ZIP"); // name of the argument as per wsdl document
property.setValue(zipCode); // value of the property

// 3. Add the property to the request object        
request.addProperty(property);

// .. this request will then be passed in to the SoapEnvelope
// e.g. envelope.setOutputSoapObject(parameter);

Processing service response

Now to the final part of the process; deciphering the service response received from the provider.

  1. Detecting the validity of the response: Firstly, we have to detect whether our service request succeeded or not. The following code snippet shows how to detect this:
    // after calling the web service using HTTP Transport ..
    
    if (envelope.bodyIn instanceof SoapObject) { // SoapObject = SUCCESS
       SoapObject soapObject = (SoapObject) envelope.bodyIn;
       result = parseSOAPResponse(soapObject);
    } else if (envelope.bodyIn instanceof SoapFault) { // SoapFault = FAILURE
       SoapFault soapFault = (SoapFault) envelope.bodyIn;
       throw new Exception(soapFault.getMessage());
    }
    
    ...
    private CityForecastBO parseSOAPResponse(SoapObject response) {
            CityForecastBO cityForecastResult = null;
            SoapObject cityForecastNode = (SoapObject) response.getProperty("GetCityForecastByZIPResult");
    
            // ... 
            // more code in here to populate the CityForecastBO object from the 
            // response object (illustrated in the steps below)
    
            return cityForecastResult;
    }

  2. Parsing the web service response in a success case: The code for parsing depends on web service you invoke (i.e. wsdl specification).
    A snippet of the SOAP response received is shown below (you can test this out by entering the zip code 12345 into the request parameter here:
    <soap:Body>
        <GetCityForecastByZIPResponse xmlns="http://ws.cdyne.com/WeatherWS/">
          <GetCityForecastByZIPResult>
            <Success>true</Success>
            <ResponseText>City Found</ResponseText>
            <State>NY</State>
            <City>Schenectady</City>
            <WeatherStationCity>Albany</WeatherStationCity>
            <ForecastResult>
              <Forecast>
                <Date>2012-10-16T00:00:00</Date>
                <WeatherID>2</WeatherID>
                <Desciption>Partly Cloudy</Desciption>
                <Temperatures>
                  <MorningLow>45</MorningLow>
                  <DaytimeHigh>55</DaytimeHigh>
                </Temperatures>
                <ProbabilityOfPrecipiation>
                  <Nighttime>30</Nighttime>
                  <Daytime>20</Daytime>
                </ProbabilityOfPrecipiation>
              </Forecast>
            </ForecastResult>
          </GetCityForecastByZIPResult>
        </GetCityForecastByZIPResponse>
      </soap:Body>

    The following code snippets demonstrate how we can use kSOAP library to parse the response xml.

  3. Reading simple (top-level) parameters:
    SoapObject cityForecastNode = (SoapObject) response.getProperty("GetCityForecastByZIPResult");
    // see the wsdl for the definition of "ForecastReturn" (which can be null/empty)
    // i.e. <s:element name="GetCityForecastByZIPResponse"> element 
    if (cityForecastNode != null) {
        // see <s:complexType name="ForecastReturn"> element for definition of "ForecastReturn"
               
        // "Success" is a mandatory node, so no need to do any null checks.
        boolean isSuccess = Boolean.parseBoolean(cityForecastNode.getPrimitivePropertySafelyAsString("Success"));
                        
        String responseText =  cityForecastNode.getPrimitivePropertySafelyAsString("ResponseText");
        String state =  cityForecastNode.getPrimitivePropertySafelyAsString("State");
        String city =  cityForecastNode.getPrimitivePropertySafelyAsString("City");
        String weatherStationCity =  cityForecastNode.getPrimitivePropertySafelyAsString("WeatherStationCity");
    }

    The getPrimitivePropertySafelyAsString(String) method allows us to extract any primitive property of the SoapObject as a String. If the property value is not present (which is the case if the attribute is optional), then we will get a null value back from this method.

  4. Reading array objects: If you take a closer look at the XML snippet for the service response, you will see that the ForecastResult node is an array consisting of zero or more Forecast nodes. We can iterate through these array structures using a simple for loop as shown below:
    // "ForecastResult" (an array) can be empty according to the wsdl definition. Therefore, we do a null check before processing
    // any data.
    SoapObject forecastResultsNode = (SoapObject) cityForecastNode.getPropertySafely("ForecastResult");
    if (forecastResultsNode != null) {
        // Definition for Forecast node can be found at <s:complexType name="Forecast"> element in wsdl
        for (int idx = 0; idx < forecastResultsNode.getPropertyCount(); idx++) {
             SoapObject forecastNode = (SoapObject) forecastResultsNode.getProperty(idx);
                        
             String date =  forecastNode.getPrimitivePropertySafelyAsString("Date");
             short weatherId =  Short.parseShort(forecastNode.getPrimitivePropertySafelyAsString("WeatherID"));
        }
    }

    There are few interesting points about the code snippet above:

    1. getPropertyCount() method allows us to get the total number of child nodes within a SoapObject; in this case it happens to be the same as the number of Forecast nodes.
    2. forecastResultsNode.getProperty(idx) allows us to retrieve a child node (i.e. SoapObject) at a given position.
    3. According to the wsdl for the weather service, ForecastResult is an optional node. Therefore, we need to do a null check before parsing the forecastResultsNode object.
  5. Reading values from nested nodes: As per wsdl definition, both “Temperatures” and “ProbabilityOfPrecipiation” nodes are nested complex types within a Forecast node. We can extract nested complex types within a SoapObject using the getProperty(String nodeName) method as illustrated below.
    // "Temperatures" and "ProbabilityOfPrecipiation" nodes are mandatory within a "Forecast" node
        //      <s:element minOccurs="1" maxOccurs="1" name="Temperatures" type="tns:temp"/>
        //      <s:element minOccurs="1" maxOccurs="1" name="ProbabilityOfPrecipiation" type="tns:POP"/>
        SoapObject temperatureNode = (SoapObject)forecastNode.getProperty("Temperatures");
        String moringLowTemp =  temperatureNode.getPrimitivePropertySafelyAsString("MorningLow");
        String daytimeHighTemp =  temperatureNode.getPrimitivePropertySafelyAsString("DaytimeHigh");
        forecast.setTemperature(moringLowTemp, daytimeHighTemp);
                        
        SoapObject precipicationProbabilityNode = (SoapObject)forecastNode.getProperty("ProbabilityOfPrecipiation");
        String nightTimePercentage =  precipicationProbabilityNode.getPrimitivePropertySafelyAsString("Nighttime");
        String dayTimePercentage =  precipicationProbabilityNode.getPrimitivePropertySafelyAsString("Daytime");
        forecast.setPrecipiationProbability(nightTimePercentage, dayTimePercentage);

Wrap up

In this totorial, I have demonstrated how to:

  1. Set up an Android project with kSOAP2 library
  2. How to create a SOAP request (with an without parameters) and invoke a web service
  3. How to parse the web service response (extracting both simple and complex types)

The full source code for the above code snippets can be found at my GitHub repository. The GetCityForecastByZipCodeTask.java and GetWeatherInformationTask.java classes (in com.example.ksoap2.wsclient.weather package) contains the core logic of using kSOAP2 library to invoke web services.

Please feel free to get in touch with me if you have any questions or suggestions.


Viewing all articles
Browse latest Browse all 10

Latest Images

Trending Articles



Latest Images