Friday, November 29, 2013

Android Webservices -KSOAP2 / Complex Object Types / Array

In this tutorial I'll tell you how to invoke a (SOAP) web service using android. For your knowledge I'll tell you that, when comparing with the REST and JSON, the SOAP is not the best way to exchange data with android. Anyway this post is about SOAP.So let's play with SOAP :D . You can download all source codes using the links at the bottom of the post.

Basically android does not provide any kind of SOAP library. Therefore either we have to manually create a soap request and handle it or we can use an external library. I'll choose the second approach. In this tutorial I'm going to use the KSOAP2 library. You can download it from here.

When we are talking about web services, for our purpose let's divide those services based on output.
1. Primitive type output - services which return a string , int , double ... etc
2. Complex type output - services which return a complex type object like Person{name,age,address}
3. Array of objects

Primitive type


In here what we are going to do is create a simple converter which convert Celsius to Fahrenheit.
Web service : http://www.w3schools.com/webservices/tempconvert.asmx
WSDL : http://www.w3schools.com/webservices/tempconvert.asmx?WSDL

First you have to create a new project , add buttons and text views, link those with the activity. I assumed that kind of basic things you can do easily. In my design nothing special, I just added a edit text to read the value, a button and a text view to show the result.



Now create a new activity, I named it as PrimitiveTypeActivity.  For call a soap method using ksoap we need these 4 things. You can find those information using the WSDL.
( Soap Action = Namespace + Method name )

private final String NAMESPACE="http://www.w3schools.com/webservices/";
private final String URL = "http://www.w3schools.com/webservices/tempconvert.asmx?WSDL";
private final String SOAP_ACTION="http://www.w3schools.com/webservices/CelsiusToFahrenheit";
private final String METHOD_NAME="CelsiusToFahrenheit";





After that what I'm going to do is call the web service when the button is pressed.

btnConvert.setOnClickListener(new OnClickListener() {
   
   @Override
   public void onClick(View v) {
    // TODO Auto-generated method stub
    String inputString = editTextCValue.getText().toString();
    ConvertorAsyncTask task = new ConvertorAsyncTask();
    task.execute(inputString);
   }
  });


Here you can see I'm creating a new ConvertorAsyncTask variable and call the execute function of it. When calling the execute function I'm passing the value which is read from the text view. So what is happening in ConvertorAsyncTask ?

class ConvertorAsyncTask extends AsyncTask  {
//

You can see now I extended AsyncTask class and created a new inner class. When you're calling web services you have to run the process in background. Otherwise you're activity will not responding until the response is coming . If it is not responding then the android OS will identify it and try to terminate that. Therefore we have to run those in background asynchronously.

@Override
  protected String doInBackground(String... params) {
   // TODO Auto-generated method stub

   SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
   
                        //Sending the value to service
                        PropertyInfo celPI = new PropertyInfo();
   celPI.setName("Celsius");
   celPI.setValue(params[0]);
   celPI.setType(double.class);
   request.addProperty(celPI);

   SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
     SoapEnvelope.VER11);

   envelope.dotNet = true;
   envelope.setOutputSoapObject(request);
   HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);

   try {
    androidHttpTransport.call(SOAP_ACTION, envelope);
    SoapPrimitive response = (SoapPrimitive) envelope.getResponse();
    return response.toString();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   } catch (XmlPullParserException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   return null;
  }

  @Override
  protected void onPostExecute(String result) {
   // TODO Auto-generated method stub
   if (result != null)
    textViewAnswer.setText(result + " F");
   else
    textViewAnswer.setText("Result cannot get.Please try again");

  }

 }

Enter your code inside the doInBackground method. In onPostExecute I set the result to a text view.
Before run the code remember to add the permission tag to manifet file.



That's all. :D


Complex Type

Here I'm going to tell you how to retrieve a complex type object. I created the ComplexTypeActivity for this. For the UI I just added a button and a text view to display results. Let's create the complex type class.
Here I created a class which contain 3 string values. I named it as SimpleObject.

public class SimpleObject implements KvmSerializable {

 private String name;
 private String address;
 private String id;

 public String getId() {
  return id;
 }

 public void setId(String id) {
  this.id = id;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getAddress() {
  return address;
 }

 public void setAddress(String address) {
  this.address = address;
 }

 @Override
 public Object getProperty(int pid) {
  // TODO Auto-generated method stub
  switch (pid) {
  case 0:
   return this.id;

  case 1:
   return this.name;

  case 2:
   return this.address;
   
  default:
   break;

  }

  return null;
 }

 @Override
 public int getPropertyCount() {
  // TODO Auto-generated method stub
  return 3;
 }

 @Override
 public void getPropertyInfo(int index, Hashtable htable, PropertyInfo info) {
  // TODO Auto-generated method stub

  switch (index) {
  case 0:
   info.type = PropertyInfo.STRING_CLASS;
   info.name = "id";
   break;
  case 1:
   info.type = PropertyInfo.STRING_CLASS;
   info.name = "name";
   break;
  case 2:
   info.type = PropertyInfo.STRING_CLASS;
   info.name = "address";
   break;

  }

 }

 @Override
 public void setProperty(int index, Object value) {
  // TODO Auto-generated method stub
  switch (index) {
  case 0:
   this.id = value.toString();
   break;
  case 1:
   this.name = value.toString();
   break;
  case 2:
   this.address = value.toString();
   break;

  }

 }

}

Note that I implemented the KvmSerializable interface here. Using that interface KSOAP map those class properties with soap message.

Same as earlier I'm going to call the web service when pressed the button.
btnShowDetails.setOnClickListener(new OnClickListener() {
   
   @Override
   public void onClick(View v) {
    // TODO Auto-generated method stub
    LoadDetailsTask task = new LoadDetailsTask();
    task.execute("ID01");
   }
  });

class LoadDetailsTask extends AsyncTask {

  private final String NAMESPACE = "http://service.blog.anjula.com/";
  private final String URL = "http://112.135.137.22:8080/BlogWebService/BlogWebService?WSDL";
  private final String SOAP_ACTION = "http://service.blog.anjula.com/getDetails";
  private final String METHOD_NAME = "getDetails";

  SimpleObject result = new SimpleObject();

  @Override
  protected SimpleObject doInBackground(String... addId) {

   SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);

   PropertyInfo idListPropety = new PropertyInfo();

   idListPropety.setName("id");
   idListPropety.setValue(addId[0]);
   idListPropety.setType(addId[0].getClass());
   request.addProperty(idListPropety);

   SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
     SoapEnvelope.VER11);

   // envelope.dotNet = true;
   envelope.setOutputSoapObject(request);

   envelope.addMapping(NAMESPACE, "SimpleObject",
     new SimpleObject().getClass());

   HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);

   try {
    androidHttpTransport.debug = true;
    androidHttpTransport.call(SOAP_ACTION, envelope);
    SoapObject response = (SoapObject) envelope.getResponse();

    result.setId(response.getProperty("id").toString());
    result.setName(response.getProperty("name").toString());
    result.setAddress(response.getProperty("address").toString());

    return result;

   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   } catch (XmlPullParserException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }

   return null;

  }

  @Override
  protected void onPostExecute(SimpleObject result) {
   // TODO Auto-generated method stub
   if (result != null)
    textViewResult.setText("Id: " + result.getId() + "\nName: "
      + result.getName() + "\nAddress: "
      + result.getAddress());
  }

 }
//

Here you can see I'm mapping the out put using field names.

result.setId(response.getProperty("id").toString());
    result.setName(response.getProperty("name").toString());
    result.setAddress(response.getProperty("address").toString());

In URL I entered my IP address there, because I ran the service in my computer. Therefore you have to change that IP address to your IP address if you're running the server application in your computer.
After you pressed the button the program will invoke this method.

public SimpleObject getDetails(String id){
        SimpleObject result = new SimpleObject();        
        result.setId(id);
        result.setName("Anjula");
        result.setAddress("Kaduwela");        
        return result;        
    }

That's all :D

Array of Objects

Here I'm using the same complex type class which I mentioned above. My activity name is ArrayTypeActivity. 



class LoadDetailsTask extends AsyncTask {

  private final String NAMESPACE = "http://service.blog.anjula.com/";
  private final String URL = "http://112.135.137.22:8080/BlogWebService/BlogWebService?WSDL";
  private final String SOAP_ACTION = "http://service.blog.anjula.com/getArrayDetails";
  private final String METHOD_NAME = "getArrayDetails";
  SimpleObject result[] = new SimpleObject[3];

  @Override
  protected SimpleObject[] doInBackground(String... idList) {

   SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
   PropertyInfo idListPropety = new PropertyInfo();  
   idListPropety.setName("id");
   idListPropety.setValue(idList[0]);
   idListPropety.setType(idList[0].getClass());
   request.addProperty(idListPropety);
   
   SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
     SoapEnvelope.VER11);

   // envelope.dotNet = true;
   envelope.setOutputSoapObject(request);

   envelope.addMapping(NAMESPACE, "SimpleObject",
     new SimpleObject().getClass());

   HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);

   try {
    androidHttpTransport.debug = true;
    androidHttpTransport.call(SOAP_ACTION, envelope);

    Vector response = (Vector) envelope
      .getResponse();

    for (int i = 0; i < response.size(); i++) {
     SoapObject tempObject = response.get(i);
     if (tempObject != null) {
      SimpleObject temp = new SimpleObject();
      temp.setId(tempObject.getProperty("id").toString());
      temp.setName(tempObject.getProperty("name").toString());
      temp.setAddress(tempObject.getProperty("address")
        .toString());
      result[i] = temp;
     }
    }

    // SoapPrimitive response = (SoapPrimitive)
    // envelope.getResponse();

    return result;

   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   } catch (XmlPullParserException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }

   return null;

  }

  @Override
  protected void onPostExecute(SimpleObject[] result) {
   // TODO Auto-generated method stub
   // super.onPostExecute(result);
   String text = "";
   if (result == null) {
    Log.d("NULL", "This is null");
   } else {
    for (int i = 0; i < result.length; i++) {
     if (result[i] != null) {
      
      text=text+"Id: " + result[i].getId() + "\nName: "
        + result[i].getName() + "\nAddress: "
        + result[i].getAddress()+"\n";

     }
    }
    textViewResult.setText(text);
   }

  }

 }

//

Here note that instead of retrieving a single object we are retrieving an array of objects. So we have to handle that using Vectors.
Vector response = (Vector) envelope
      .getResponse();

    for (int i = 0; i < response.size(); i++) {
     SoapObject tempObject = response.get(i);
     if (tempObject != null) {
      SimpleObject temp = new SimpleObject();
      temp.setId(tempObject.getProperty("id").toString());
      temp.setName(tempObject.getProperty("name").toString());
      temp.setAddress(tempObject.getProperty("address")
        .toString());
      result[i] = temp;
     }
    }
//



You can download web service from (Netbeans) here and to download the android source code (eclipse) click here. 

3 comments: