lunes, 8 de abril de 2013

Android: Invocación a servicios web con HttpURLConnection


Hay varias formas de invocar a servicios web con Android. Hoy, basándonos en lo aprendido en el post Android: Consultar datos de una web ( tareas asíncronas) veremos una de ellas.
Así pues, teniendo bien fresco tanto la creación del servicio web creado en java en el post JAVA: Crear un servicio web básico con NetBeans 7.x (aunque también podemos hacerlo con .NET) y el comentado anteriormente, nos ponemos manos a la obra.

Dibujando la pantalla

Una vez creado el proyecto y modificado el manifest para tener acceso a internet y para poder debuggear, añadiremos los siguientes elementos a nuestro layout:
  • TextView con texto "URL del Servicio Web (sin WSDL):"
  • EditText con id edtUrl
  • TextView con texto "Texto de la Invocación:"
  • EditText con id edtRequest y propiedad inputType con valor textMultiLine
  • ProgressBar con id pbLoading
  • Button con id btnGo y texto "Invocar Servicio"


La pantalla nos quedará algo así:



En resumen:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/btnGo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/TextView01"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:text="Invocar Servicio" />

    <TextView
        android:id="@+id/TextView01"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="URL del Servicio Web (sin WSDL):" />

    <EditText
        android:id="@+id/edtUrl"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/TextView01"
        android:layout_alignRight="@+id/btnGo"
        android:layout_below="@+id/TextView01"
        android:ems="10" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/edtUrl"
        android:layout_below="@+id/edtUrl"
        android:text="Texto de la Invocación:" />

    <EditText
        android:id="@+id/edtRequest"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/btnGo"
        android:layout_alignLeft="@+id/textView1"
        android:layout_alignRight="@+id/edtUrl"
        android:layout_below="@+id/textView1"
        android:ems="10"
        android:inputType="textMultiLine" >

        <requestFocus />
    </EditText>

    <ProgressBar
        android:id="@+id/pbLoading"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:visibility="invisible" />
       
</RelativeLayout>

Modificando la clase MainActivity

En esta clase haremos pocos cambios respecto al post Android: Consultar datos de una web ( tareas asíncronas). Lo único que cambiaremos en nuestra función cargaObjetosPantalla con los nuevos elementos que hemos añadido, y una invocación a inicializarValoresTextViews, que básicamente lo que hace es inicializar los EditText con la URL y el mensaje SOAP para invocar al servicio web.

       private void cargaObjetosPantalla()
       {
             //cargamos los componentes
             edtUrl = (EditText)findViewById(R.id.edtUrl);
             edtRequest = (EditText)findViewById(R.id.edtRequest);
             btnGo = (Button)findViewById(R.id.btnGo);
             pbLoading = (ProgressBar)findViewById(R.id.pbLoading);

             //asignamos valores
             inicializarValoresTextViews();
            
             //creamos eventos
             btnGo.setOnClickListener(new OnClickListener() {
                    public void onClick(View v) {
                           if (!pbLoading.isShown())
                                  cargaWeb();
                    }
             });
       }
      
       private void inicializarValoresTextViews()
       {
             //URL del servicio web que vamos a invocar
             String strUrl = "http://10.114.201.59:8080/demoJavaWS/WSDemo";
             //request soap con el que invocaremos al WS
             //este mensaje lo hemos extraido de la invocación
             //de ejemplo de SOAP-UI
             String strRequest = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:dem='http://demojavaws.com/'><soapenv:Header/><soapenv:Body><dem:hello><name>Jose Carlos</name></dem:hello></soapenv:Body></soapenv:Envelope>";
            
             edtUrl.setText(strUrl);
             edtRequest.setText(strRequest);
       }

Finalmente, haremos un último cambio. En la función cargaWeb, en lugar de enviar el TextView para que nos muestre el resultado, le pasaremos el componente edtRequest que contiene el mensaje a enviar al servicio, y que nos mostrará el resultado de la invocación.

private void cargaWeb()
{
       AsyncLoadWeb loadweb = new AsyncLoadWeb(this.edtRequest, this.pbLoading);
       URL url = null;
       try {
             url = new URL(edtUrl.getText().toString());
       } catch (MalformedURLException e) {
             e.printStackTrace();
       }
       loadweb.execute(url);
}

Con este cambio, resulta más que evidente que tendremos que cambiar la clase AsyncLoadWeb.

Modificando la clase AsyncLoadWeb

Los cambios que tendremos que aplicar en esta clase que creamos en el post anterior Android: Consultar datos de una web ( tareas asíncronas) comienzan con la modificación del constructor para que reciba el EditText en lugar del TextView.

//recibiremos objetos de la
//pantalla para interactuar con ellos
private TextView texto;
private ProgressBar loading;

public AsyncLoadWeb(EditText texto, ProgressBar loading){
       super();
       this.texto = texto;
       this.loading = loading;
}

Tras esto, en el método doInBackground deberemos modificar para que el objeto HttpURLConnection defina el método de petición a POST y algunas propiedades:

//establecemos el metodo de petición
urlConnection.setRequestMethod("POST");
//y algunas propiedades
urlConnection.setRequestProperty("Content-Type",
             "text/xml;charset=UTF-8");
//deberiamos indicar tb el tamaño,
//pero la mayoria de las veces no es necesario
//urlConnection.setRequestProperty("Content-Length",
//           "" + texto.getText().toString().length());

Después, hay que indicarle que vamos a usar tanto el input como el output

//trabajeremos con el input
//y el output, asi que lo indicamos
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);

Y finalmente, indicar en el output el texto de la petición (en bytes). Para esto hay dos opciones. Nosotros usaremos la más sencilla:

//y escribimos la peticion (hay dos opciones):
// OPCION A: la más  correcta
/*DataOutputStream wr =
  new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(texto.getText().toString());
wr.flush ();
wr.close ();*/
//OPCION B: la más práctica
urlConnection.getOutputStream().write(
             texto.getText().toString().getBytes());

El resto quedará igual, por lo que la función queda en:

//ejecución de la operación en background
protected String doInBackground(URL... urls)
{
       URL url = urls[0];
       HttpURLConnection urlConnection;
      
       try {
             urlConnection = (HttpURLConnection)url.openConnection();
             //establecemos el metodo de petición
             urlConnection.setRequestMethod("POST");
             //y algunas propiedades
             urlConnection.setRequestProperty("Content-Type", "text/xml;charset=UTF-8");
             //deberiamos indicar tb el tamaño,
             //pero la mayoria de las veces no es necesario
             //urlConnection.setRequestProperty("Content-Length",
             //           "" + texto.getText().toString().length());

             //trabajeremos con el input
             //y el output, asi que lo indicamos
             urlConnection.setDoInput(true);
             urlConnection.setDoOutput(true);
            
             //y escribimos la peticion (hay dos opciones):
             // OPCION A: la más  correcta
             /*DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
             wr.writeBytes(texto.getText().toString());
             wr.flush ();
             wr.close ();*/
             //OPCION B: la más práctica
             urlConnection.getOutputStream().write(texto.getText().toString().getBytes());
      
             try {
                    InputStream in = new BufferedInputStream(urlConnection.getInputStream());
                    res = readStream(in);
             } catch (Exception e) {
                    res = "Ha ocurrido un error en doInBackground (try 2): " + e.toString();
             } finally {
                    urlConnection.disconnect();
             }
      
       } catch (IOException e1) {
             res = "Ha ocurrido un error en doInBackground (try 1): " + e1.toString();
       }
       return res;
}

Y el resto lo dejamos todo igual.

Probado

Al ejecutar podremos ver esto:



Si ocurre algún error:



Y si todo va bien, nos responderá:


Código de ejemplo aquí