soap_pattern

Integración Retrofit y Servicios SOAP

Conocer como se integran servicios web en aplicaciones móviles es un “Must” que todo desarrollador debe conocer. La mayoría de sistemas han adoptado los servicios Web del tipo REST / JSON, pero aún existen sistemas, que por antigüedad o por integración con otros servicios, deben utilizar servicios del tipo SOAP/WSDL. A lo largo de los años, un desarrollador tocará ambos tipos de servicios web, por lo que es muy recomendable saber como se introducen en nuestros proyectos.

 


SOAP vs REST

No voy a entrar en el debate de cual es mejor, ya que ambos hacen la función que tienen. Son tecnologías que son complementarias, aunque no es normal encontrarse las dos en un mismo proyecto.

Algunas de las características más importantes son:

  • Los servicios SOAP funcionan con un fichero de definición del servicio (WSDL), en el que se definen los tipos de datos que se usarán, los métodos que tiene el servicio, y las respuestas. Por el contrario, una petición REST no tiene este tipo de fichero, por lo que tendremos que saber de antemano el nombre de los métodos, y el tipo de datos que se le pasa.
  • Las servicios REST suelen ser más rápidos que los servicios SOAP. Esto es debido a la estructura de los datos. JSON es más ligero, mientras que el XML que se genera en un servicio SOAP es mucho mas pesado. En servicios SOAP hay que mandar cabeceras y “namespaces” (lo que es en desarrollo no tienen ninguna importancia), que en un JSON no aparecen.
  • Los servicios REST, al ser más ágiles, se han creado más librerías para dispositivos móviles (Gson, Jackson, Moshi, … ) y mas facilidad de integración, mientras que para los servicios SOAP no hay tal abanico de oportunidades (Ksoap2, SimpleXML).

Ejemplo práctico

Para el ejemplo que vamos a realizar, utilizaremos un servicio web gratuito de WebServiceX, el cual nos ofrecerá un listado de ciudades de Estados Unidos con sus códigos postales a través del nombre de la ciudad, que será lo que le pasaremos al servicio.

Definición del Servicio

La petición tendrá el siguiente formato:

<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <GetInfoByCity xmlns="http://www.webserviceX.NET">
      <USCity>string</USCity>
    </GetInfoByCity>
  </soap12:Body>
</soap12:Envelope>

Mientras, la respuesta tendrá el siguiente formato:

<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <GetInfoByCityResponse xmlns="http://www.webserviceX.NET">
      <GetInfoByCityResult>xml</GetInfoByCityResult>
    </GetInfoByCityResponse>
  </soap12:Body>
</soap12:Envelope>

Para probar el servicio antes del desarrollo, podremos hacerlo simulando una petición POST con alguna herramienta de servicios. En mi caso, suelo usar POSTMAN.

soap_postman

Gracias al uso de esta herramienta, podemos observar la salida del webservice, así poder crear el objeto de la respuesta correcto.

Librerías

Utilizaremos las librerías de retrofit y okHttp para las peticiones. Ademas utilizaremos el convertidor SimpleXml para retrofit

compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
compile 'com.squareup.okhttp3:okhttp:3.3.1'
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.3.1'

compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile ('com.squareup.retrofit2:converter-simplexml:2.1.0'){
    exclude group: 'stax', module: 'stax-api'
    exclude group: 'stax', module: 'stax'
    exclude group: 'xpp3', module: 'xpp3'
}

Configuración de Retrofit y OkHttp

La creación de la instancia de Retrofit tendrá que llevar consigo el Converter de SimpleXml con el interceptor necesario:

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();

interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

Strategy strategy = new AnnotationStrategy();

Serializer serializer = new Persister(strategy);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .connectTimeout(2, TimeUnit.MINUTES)
            .writeTimeout(2, TimeUnit.MINUTES)
            .readTimeout(2, TimeUnit.MINUTES)
            .build();

Retrofit retrofit =  new Retrofit.Builder()
    .addConverterFactory(SimpleXmlConverterFactory.create(serializer))
    .baseUrl("http://www.webservicex.net/")
    .client(okHttpClient)
    .build();

Creación del objeto API

Será la interfaz que se utilizará Retrofit para la creación de la API.

public interface UsStatesApi {

    @Headers({
            "Content-Type: text/xml",
            "Accept-Charset: utf-8"
    })
    @POST("/uszip.asmx")
    Call<UsStatesResponseEnvelope> requestStateInfo(@Body UsStatesRequestEnvelope body);

}

Creación de las clases para la request y response

Los objetos tanto de la petición como la respuesta tienen la misma forma de creación, por lo que solo pondremos aquí los de la petición.

@Root(name = "soap12:Envelope")
@NamespaceList({
        @Namespace( prefix = "xsi", reference = "http://www.w3.org/2001/XMLSchema-instance"),
        @Namespace( prefix = "xsd", reference = "http://www.w3.org/2001/XMLSchema"),
        @Namespace( prefix = "soap12", reference = "http://www.w3.org/2003/05/soap-envelope")
})
public class UsStatesRequestEnvelope {

    @Element(name = "soap12:Body", required = false)
    private UsStatesRequestBody body;

    public UsStatesRequestBody getBody() {
        return body;
    }

    public void setBody(UsStatesRequestBody body) {
        this.body = body;
    }
}
@Root(name = "soap12:Body", strict = false)
public class UsStatesRequestBody {

    @Element(name = "GetInfoByCity",required = false)
    private UsStatesRequestData usStatesRequestData;

    public UsStatesRequestData getUsStatesRequestData() {
        return usStatesRequestData;
    }

    public void setUsStatesRequestData(UsStatesRequestData usStatesRequestData) {
        this.usStatesRequestData = usStatesRequestData;
    }

}
@Root(name = "GetInfoByState", strict = false)
@Namespace(reference = "http://www.webserviceX.NET")
public class UsStatesRequestData {

    @Element(name = "USCity", required = false)
    private String city;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

}

Por último, para hacer la petición deberemos hacer lo siguiente:

UsStatesRequestEnvelope envelope = new UsStatesRequestEnvelope();

UsStatesRequestBody body = new UsStatesRequestBody();

UsStatesRequestData data = new UsStatesRequestData();

data.setCity( cityName );

body.setUsStatesRequestData(data);

envelope.setBody(body);

Call<UsStatesResponseEnvelope> call = usStatesApi.requestStateInfo(envelope);

call.enqueue(new retrofit2.Callback<UsStatesResponseEnvelope>() {

    @Override
    public void onResponse(Call<UsStatesResponseEnvelope> call, final Response<UsStatesResponseEnvelope> response) {

       //DO WHAT YOU WANT 

    }

    @Override
    public void onFailure(Call<UsStatesResponseEnvelope> call, Throwable t) {
 
        //ERROR!!
                
    }

});

 

Puedes ver un ejemplo completo en GITHUB


3 Comments

  • Vishman

    01 de agosto de 2016

    Hi Thanks for the source code and tutorial.
    I found your article through StackOverflow Q&A thread.
    http://stackoverflow.com/a/37962683/1752988
    But your blog post is not detailed enough for me.

    Could you explain about the following implementation?
    “ZipCodeComponent” interface and “DaggerZipCodeComponent” class?

    Actually your implementation is good, but it’s complicated for beginners who are in search of how to use Retrofit to fetch some SOAP web service.

    So please if you can explain your implementation a bit.

    Thanks

    Reply
    • Alejandro

      01 de agosto de 2016

      “ZipCodeComponent” class is a dagger2 class. Dagger is a library for dependency injection for java/android. “DaggerZipCodeComponent” is an autogenerated class. In “ZipCodeComponent” class, we define the classes or objects we want to be injected in our graph.
      If you don’t want to use dependency injection, you can initialize the objects where they are used. For “InteractorExecutor” or “MainThread” you can use Singleton pattern. You can see singleton pattern here

      public class DbManager {
      private static volatile DbManager instance = null;
      private DbManager() {
      // No instances created from outside.
      }
      public static DbManager getInstance() {
      if (instance == null) {
      synchronized (DbManager.class) {
      if (instance == null) {
      instance = new DbManager();
      }
      }
      }
      return instance;
      }
      }

      Reply
      • Vishman

        02 de agosto de 2016

        @Alejandro Thanks for the prompt reply. At least now I know what Dagger is! :-) There’s no mentioning about it even through the source code as I can remember. However out of your source code, I separated what is essential for Retrofit and SOAP web API and now I have a working very simple example. I will let you know about it later on.

        Please give good explanation about your source code, because your implementation is really great, but for beginners in Android we need some more guidance.

        Thanks again! :-)

        Reply

Deja un comentario

dieciocho − siete =