Monthly Archives: November 2011

Llamando a un servicio Web desde Android

English

Este documento describe como conectarse a un servicio Web desde una aplicación de Android. Se asume que el lector tiene conocimiento básico en este sistema operativo, así como también de servicios Web y mensajes SOAP. Si se requiere profundizar en estos temas, se pueden consultar las siguientes ligas:

http://developer.android.com/index.html
http://www.w3schools.com/webservices/default.asp
http://www.w3schools.com/soap/default.asp

La aplicación que a continuación se describe se divide en 2 partes: el código requerido para conectarse a un servicio Web y el escenario para verificar el funcionamiento de la aplicación.

Las siguientes librerías y aplicaciones son utilizadas:

– Windows 7

– Eclipse Galileo (Android DDMS, Android Development Tools)

– Librería Ksoap2-android

La siguiente aplicación Android tiene como objetivo conectarse a un servicio Web, el cual se encarga de convertir grados Fahrenheit a Celsius. Los detalles de este servicio y el WSDL se encuentran en la siguiente URL:

http://www.w3schools.com/webservices/tempconvert.asmx

1.1) Primeramente, creamos un proyecto Android. Si el plugin fue correctamente instalado, se debe de observar la opción “Android project” dentro de la categoría “New”, bajo el menú de “File”:

image

1.2) Se puede utilizar cualquier nombre para el proyecto, en mi caso, estoy utilizando webServiceSample. La versión de Android que se utiliza para el desarrollo es la 2.3, pero el código debería funcionar correctamente en  versiones anteriores. En el apartado de “Propiedades”, se especifica el nombre de la aplicación, el nombre del paquete y nombre de la actividad. Estos campos se llenaron de la siguiente forma: Web Service Sample, com.webservice.test, WSMain.

image

Si se desea crear un proyecto de pruebas, damos clic en NEXT, en caso contrario seleccionamos FINISH. Para crear un proyecto de pruebas, solo hay que activar la opción de “create a Test Project” y Eclipse llenara de manera automática los campos requeridos.

image

1.3) Eclipse crea automáticamente un esqueleto de la actividad principal y hace las asociaciones correspondientes en el archivo de AndroidManifest. A continuación, modificaremos el layout de la aplicación para que contenga un campo de edición y un botón. Para esto, se puede utilizar el layout grafico o el editor de XML. El layout debe verse así:

image

Código:

<?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"
    >
	<TextView
		android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="Fahrenheit: "
    />
    <EditText
    	android:id="@+id/inputTemp"
    	android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:inputType="numberSigned"
	    android:background="@android:drawable/editbox_background"
	    />
    <Button
    	android:layout_width="wrap_content"
    	android:text="Convert"
    	android:layout_height="wrap_content"
    	android:id="@+id/convert"
    	android:onClick="convertFahrenheitToCelsius"
   	/>
	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:id="@+id/response"
		android:gravity="center_horizontal"
	/>

</LinearLayout>

1.4) Antes de iniciar con la codificación de la actividad, es necesario importar la librería ksoap2 al proyecto. Para esto nos vamos a la configuración del build path del proyecto y agregamos un JAR externo:

clip_image002

1.5) Ahora nos vamos a la actividad principal (WSMain) para comenzar la codificación de la aplicación. Iniciaremos definiendo los componentes del mensaje SOAP (namespace, URL, soap action, method name):

public class WSMain extends Activity {

	private final String NAMESPACE = "http://tempuri.org/";
	private final String URL = "http://www.w3schools.com/webservices/tempconvert.asmx";
	private final String SOAPACTION = "http://tempuri.org/FahrenheitToCelsius";
	private final String METHOD = "FahrenheitToCelsius";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

    }

Estos valores provienen del mensaje SOAP definido para este servicio Web:

POST /webservices/tempconvert.asmx HTTP/1.1
Host: www.w3schools.com
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/FahrenheitToCelsius"

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <FahrenheitToCelsius xmlns="http://tempuri.org/">
      <Fahrenheit>string</Fahrenheit>
    </FahrenheitToCelsius>
  </soap:Body>
</soap:Envelope>

1.6) La llamada al servicio Web se hace a través de una función publica llamada convertFahrenheitToCelsius que recibe como parámetro un objeto de tipo View (la razón de esto se explica mas adelante).

Antes de construir el mensaje SOAP, necesitamos tener una referencia de los TextViews que contendrán los valores de entrada y salida:

public void convertFahrenheitToCelsius(View  v)
    {
    	TextView response_tv = (TextView)findViewById(id.response);
    	final TextView input_tv = (TextView)findViewById(id.inputTemp);
    	String fahrenheit_value = input_tv.getText().toString();

    	if(fahrenheit_value == null || fahrenheit_value.length() == 0)
    	{
    		response_tv.setText("Error!");
    		return;
    	}

....

La variable fahrenheit_value guardara los grados Fahrenheit que se desean convertir. Una vez capturada esta información, se evalúa que este valor no este vacío. Esta evaluación no es necesaria, solamente se incluye para utilizar este escenario durante la creación de casos de prueba.

1.7) El siguiente paso consiste en crear el mensaje SOAP a través de la clase SOAPObject de la librería ksoap2. Dentro de este objeto se encontrara la información referente al servicio Web como el nombre del método que será llamado, el valor que sea desea convertir, etc.

...
SoapObject request = new SoapObject(NAMESPACE, METHOD);

        PropertyInfo FahrenheitProp = new PropertyInfo();
        FahrenheitProp.setName("Fahrenheit");
        FahrenheitProp.setValue(fahrenheit_value);
        FahrenheitProp.setType(String.class);
        request.addProperty(FahrenheitProp);

        SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
        envelope.dotNet = true;
        envelope.setOutputSoapObject(request);
...

NOTA: A la propiedad envelope.dotNet se le da un valor de “true” debido a que el servicio Web al cual nos queremos conectar esta codificado en .NET.

1.8) Una vez construido el mensaje SOAP, hay que establecer la conexión con el servicio Web. Esto se hace a través de una solicitud HTTP:

...
HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
        try
        {
        	androidHttpTransport.call(SOAPACTION, envelope);
        	SoapPrimitive response = (SoapPrimitive)envelope.getResponse();
        	response_tv.setText(fahrenheit_value + " F = " + response.toString() + " C");

        }catch(Exception e)
        {
        	e.printStackTrace();
        }
...

Dentro de este código se hace la llamada al servicio Web y de igual forma se recibe la respuesta. La cual es convertida a String y escrita al objeto TextView.

1.9) El código para la actividad WSMain debe verse de la siguiente forma:

package com.webservice.test;

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.HttpTransportSE;

import com.webservice.test.R.id;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class WSMain extends Activity {

	private final String NAMESPACE = "http://tempuri.org/";
	private final String URL = "http://www.w3schools.com/webservices/tempconvert.asmx";
	private final String SOAPACTION = "http://tempuri.org/FahrenheitToCelsius";
	private final String METHOD = "FahrenheitToCelsius";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

    }

    public void convertFahrenheitToCelsius(View  v)
    {
    	TextView response_tv = (TextView)findViewById(id.response);
    	final TextView input_tv = (TextView)findViewById(id.inputTemp);
    	String fahrenheit_value = input_tv.getText().toString();

    	if(fahrenheit_value == null || fahrenheit_value.length() == 0)
    	{
    		response_tv.setText("Error!");
    		return;
    	}

    	SoapObject request = new SoapObject(NAMESPACE, METHOD);

        PropertyInfo FahrenheitProp = new PropertyInfo();
        FahrenheitProp.setName("Fahrenheit");
        FahrenheitProp.setValue(fahrenheit_value);
        FahrenheitProp.setType(String.class);
        request.addProperty(FahrenheitProp);

        SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
        envelope.dotNet = true;
        envelope.setOutputSoapObject(request);
        HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
        try
        {
        	androidHttpTransport.call(SOAPACTION, envelope);
        	SoapPrimitive response = (SoapPrimitive)envelope.getResponse();
        	response_tv.setText(fahrenheit_value + " F = " + response.toString() + " C");

        }catch(Exception e)
        {
        	e.printStackTrace();
        }
    }
}

1.10) En este momento ya tenemos todo el código necesario para comunicarnos con el servicio Web. Sin embargo, al ejecutar la aplicación, esta fallara por 2 razones: La función convertFahrenheitToCelsius no es invocada por nadie y la aplicación no tiene permisos para acceder a Internet.

El primer problema se soluciona editando de nuevo el layout de la aplicación. Específicamente, se edita el componente Button y se le agrega la siguiente propiedad:

android:onClick=”convertFahrenheitToCelsius”

<Button
    	android:layout_width="wrap_content"
    	android:text="Convert"
    	android:layout_height="wrap_content"
    	android:id="@+id/convert"
    	android:onClick="convertFahrenheitToCelsius"
   	/>

La definición de este componente dice que la función utilizada por la propiedad OnClick debe ser una función PUBLICA y debe recibir como parámetro un objeto del tipo VIEW.

La segunda restricción se soluciona editando el archivo de AndroidManifest.xml y agregando la siguiente etiqueta:

<uses-permission android:name=”android.permission.INTERNET”></uses-permission>

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.webservice.test"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".WSMain"
                  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>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest>

1.11) Si ejecutamos la aplicación veremos lo siguiente:

image

Si la aplicación no esta dando la salida deseada, se puede utilizar la perspectiva DDMS para depurar el código.

2.1) La funcionalidad puede verificarse a través de JUnit, utilizando el proyecto de pruebas (webServiceSampleTest) generado por Eclipse al momento de crear la solución principal. Para esto, hay que agregar un archivo de tipo JUnit Test Case dentro del proyecto de pruebas:

image

El nombre del caso de prueba, para este ejemplo, es InputValidation. La súper clase de la cual heredara es ActivityInstrumentationTestCase2. Y la clase que será verificada es android.test.AndroidTestCase.

Además, se seleccionan los métodos setUp() y constructor para que sean construidos automáticamente. Para mas información sobre estos métodos, se puede consultar el siguiente tutorial:

http://mobile.tutsplus.com/tutorials/android/android-sdk-junit-testing/

2.2) El caso de prueba evaluara 3 condiciones básicas: entrada con valor positivo, entrada con valor negativo y valor vacío. Por lo tanto, lo primero que declararemos son las salidas esperadas para dichos escenarios:

public class InputValidation extends ActivityInstrumentationTestCase2<WSMain> {

	private TextView result;

	//results
	private static final String FAHR_50 = "50 F = 10 C";
	private static final String FAHR_N67 = "-67 F = -55 C";
	private static final String EMPTYVALUE = "Error!";
...

2.3) Después se modificaran los métodos de setUp y constructor. En este ultimo, lo único que se debe hacer es pasar los parámetros correspondientes a la clase padre. Para el método setUp, se hace una referencia a la actividad que deseamos probar y también al objeto TextView que almacenara la salida.

...
public InputValidation() {
		super("com.webservice.test", WSMain.class);
	}

	protected void setUp() throws Exception {
		super.setUp();
		WSMain mainActivity = getActivity();
		result = (TextView)mainActivity.findViewById(id.response);
	}
...

2.4) Iniciamos con el primer escenario, un valor decimal con signo positivo. La temperatura que se desea convertir son 50 grados Fahrenheit, por lo tanto esperaremos un valor de 10 grados Celsius como salida:

...
public void testPositiveValue()
	{
		sendKeys("5 0 ENTER "); // 50 Fahrenheit
		sendKeys("ENTER");

		//result
		String celsius_result = result.getText().toString();
		assertTrue(celsius_result, celsius_result.equals(FAHR_50));
	}
...

2.5)El segundo caso es un valor con signo negativo.  Se especificaran como entrada –67 grados Fahrenheit y la salida debe de arrojar –55 grados Celsius:

...
	public void testNegativeValue()
	{
		sendKeys("MINUS 6 7 ENTER"); // -67 Fahrenheit
		sendKeys("ENTER");

		//result
		String celsius_result = result.getText().toString();
		assertTrue(celsius_result, celsius_result.equals(FAHR_N67));
	}
...

2.6) Por ultimo, se prueba el escenario donde la entrada esta vacía. De acuerdo a la codificación que se hizo, la aplicación debe de escribir la cadena “Error!”.

...
	public void testEmptyValue()
	{
		sendKeys("ENTER ENTER");
		String celsius_result = result.getText().toString();
		assertTrue(celsius_result, celsius_result.equals(EMPTYVALUE));
	}
...

2.7) El caso de pruebas completo debe verse de la siguiente forma:

package com.webservice.test.test;

import com.webservice.test.WSMain;
import com.webservice.test.R.id;

import android.test.ActivityInstrumentationTestCase2;
import android.widget.TextView;

public class InputValidation extends ActivityInstrumentationTestCase2<WSMain> {

	private TextView result;

	//results
	private static final String FAHR_50 = "50 F = 10 C";
	private static final String FAHR_N67 = "-67 F = -55 C";
	private static final String EMPTYVALUE = "Error!";

	public InputValidation() {
		super("com.webservice.test", WSMain.class);
	}

	protected void setUp() throws Exception {
		super.setUp();
		WSMain mainActivity = getActivity();
		result = (TextView)mainActivity.findViewById(id.response);
	}

	public void testPositiveValue()
	{
		sendKeys("5 0 ENTER "); // 50 Fahrenheit
		sendKeys("ENTER");

		//result
		String celsius_result = result.getText().toString();
		assertTrue(celsius_result, celsius_result.equals(FAHR_50));
	}

	public void testNegativeValue()
	{
		sendKeys("MINUS 6 7 ENTER"); // -67 Fahrenheit
		sendKeys("ENTER");

		//result
		String celsius_result = result.getText().toString();
		assertTrue(celsius_result, celsius_result.equals(FAHR_N67));
	}

	public void testEmptyValue()
	{
		sendKeys("ENTER ENTER");
		String celsius_result = result.getText().toString();
		assertTrue(celsius_result, celsius_result.equals(EMPTYVALUE));
	}
}

2.8) Para lanzar la prueba, debemos dar clic derecho sobre el archivo InputValidation.java y seleccionar Debug As –> Android JUnit test

image

2.9) Los casos de prueba deben de correr de manera exitosa:

image