Desarrollo de interfaces dinámicas de usuario con Android y XML

Recolección de datos mediante el motor de formularios de Android

Un número de sitios web atienden a organizaciones no lucrativas que proporcionan formularios de fácil configuración y que son utilizados para realizar sondeos y recolectar datos. Este tutorial introduce una arquitectura simple para diseñar aplicaciones similares para interfaces dinámicas de usuarios de Android que permiten a quienes no son programadores recolectar datos de usuarios de dispositivos móviles. Con este tutorial podrá crear un ejemplo de un motor de formularios, tanto con un servidor como con un dispositivo móvil.

Frank Ableson, Author

Después de que terminara su carrera basquetbolista colegial sin un contrato multianual para jugar para los Lakers de Los Ángeles, Frank Ableson cambió su enfoque hacia el diseño de software informático. Disfruta solucionando problemas complejos, particularmente en las áreas de comunicaciones e interfaces de hardware. Cuando no está trabajando, está pasando el tiempo con su esposa Nikki y sus hijos. Es posible contactar a Frank escribiendo a frank@cfgsolutions.com.



25-02-2013

Antes de comenzar

Debe sentirse a gusto durante la construcción de aplicaciones de Android con Android SDK para sacar el mayor provecho de este tutorial. Una vez que haya completado este tutorial, habrá aprendido a realizar comunicaciones de aplicaciones a servidores web con HTTP(S) y cómo analizar XML con el analizador DOM. En el transcurso, creará interfaces de usuario dinámicas y personalizadas, comunicaciones multiproceso, manipuladores de mensajes y diálogos de progreso. En menor grado, aprenderá sobre AndroidManifest.xml y entornos de scripting de servidores.

Acerca de este tutorial

Acrónimos de uso frecuente

  • API: Interfaz de programación de aplicaciones
  • DOM: Modelo de objetos de documentos
  • HTML: Lenguaje de marcado de hipertexto
  • HTTP(S): Protocolo de transferencia de hipertexto seguro
  • IDE: Entorno de desarrollo integrado
  • SAX: API simple para XML
  • SDK: Kit de desarrollo de software
  • UI: Interfaz de usuario
  • URL: Localizador universal de recursos
  • XML: Lenguaje de marcado extensible

Este tutorial introduce una arquitectura para formularios dinámicos para la recolección de datos de dispositivos móviles en dispositivos Android. Comenzaré con un nivel de arquitectura y discusión elevados sobre el lugar en el que tal aplicación se ajusta en el mayor contexto de recolección de datos. Un vistazo previo del proyecto completo, que incluye cada archivo de origen, que le presenta una hoja de ruta clara sobre el objetivo de este tutorial. Como si se tratara de una receta, usted construirá la aplicación desde cero con cada clase de Java presentada correctamente y relacionada con otros aspectos de la aplicación, en especial, el modelo de datos sobre el cual se desarrollará este motor de formularios. Para finalizar, guardará los datos del formulario en un servidor y podrá examinar brevemente el lado del servidor de la aplicación.

Prerrequisitos

La Tabla 1 muestra las herramientas requeridas para este proyecto.

Tabla 1. Herramientas necesarias para este trabajo
HerramientaComentario
Eclipse y ADTEditor de códigos primario y complemento de herramientas de desarrollo de Android
Android SDKKit de desarrollo de software de Android
Servidor webCualquier tipo de servidor compatible con PHP. Puede transportar fácilmente el script a otro entorno de servidor.

Las muestras de código que he creado para este tutorial las he realizado en una MacBook con Eclipse 3.4.2 y Android SDK versión 8, que admite la versión Android 2.2. El código del tutorial no aprovecha ninguna característica específica de este SDK y la aplicación debe ejecutarse bien en las versiones de Android actuales y anteriores hasta la versión 1.5. Consulte los Recursos para acceder a los enlaces para todas las herramientas.


Recolección de datos

Comencemos con una breve discusión sobre la recolección de datos y lo fácil que puede ser implementarla cuando utilizamos un dispositivo móvil Android.

Una infraestructura Android para la recolección de datos

La recolección de datos es una tarea que existe desde antes de la era de las computadoras. Las computadoras se han convertido en un objeto de uso diario y han revolucionado la forma en la que pensamos, buscamos y consumimos información. Las compañías con mercados de capitales con cifras millonarias deben su existencia a la efectividad de almacenamiento, recuperación y administración de enormes cantidades de información que ofrecen estos dispositivos. Las bases de datos utilizadas en la actualidad están alimentadas por sistemas de diversas arquitecturas que incluyen las del tipo marco principal, servidor de cliente, aplicaciones web y, ahora, aplicaciones móviles.

Los inventarios físicos y las aplicaciones de conteo de ciclos fueron algunas de las primeras aplicaciones prácticas de la computación móvil. Estas aplicaciones consistían a menudo en recolección de datos por lotes, para las que el hardware requería una base de acoplamiento para cargar la información recolectada.

El mercado para las aplicaciones móviles ha evolucionado mucho desde aquellos primeros tiempos y la conectividad y los dispositivos inalámbricos son casi omnipresentes en muchas culturas y mercados, y han invadido virtualmente cada aspecto de nuestras vidas a diario.

Mientras los medios para recolectar datos han pasado a ser más móviles, el aspecto central de la recopilación de datos no ha cambiado significativamente. El usuario debe responder una serie de preguntas y los medios de respuesta que posee son simples. Este tutorial presenta la construcción de una infraestructura simple para la recolección de datos para teléfonos móviles con Android, aprovechando la estructura de metadatos que permite XML.


Arquitectura de la aplicación

Antes de sumergirnos en el código, examine la configuración de la aplicación para que esta sea de un muy alto nivel.

Vista rápida de la aplicación Forms Engine

Examine todos los aspectos de la aplicación Forms Engine. La Figura 1 presenta la relación de la aplicación con uno o más servidores que proveen formularios de entrada de datos de diverso contenido.

Figura 1. Arquitectura de la aplicación
Diagram of the application architecture

En la Figura 1, Form 1 proporciona la inscripción para la competición de Robotics y Form 2 solicita al usuario información sobre sus hábitos de automantenimiento. A través del protocolo HTTP(S), los formularios y la aplicación de Android se comunican para:

  • Descargar los datos de los formularios.
  • Presentar los datos de los formularios al usuario y recolectar datos específicos del dispositivo opcionales tales como imágenes tomadas con cámaras, grabaciones de sonido, localización con GPS o lecturas de brújulas.
  • Recolectar datos suministrados por el usuario.
  • Enviar datos al servidor apropiado.

El lado del servidor de este tutorial se implementa como un par de archivos: un documento XML que describe el formulario y un documento PHP que es el responsable de registrar el envío del formulario. La aplicación de Android es una aplicación nativa escrita en código Java para la cual se utiliza Android SDK y se codifica en Eclipse.

La Tabla 2 muestra los archivos de origen de la aplicación para la aplicación completa. Puede descargar el archivo comprimido que contiene todos estos archivos de origen (vea las Descargas). Examinaremos cada uno de estos archivos detalladamente en este tutorial.

Tabla 2. Los archivos de origen requeridos para la aplicación
Nombre de archivoComentario
XmlGui.javaPunto de entrada para las actividades en Android
XmlGuiForm.javaModelo y método de datos de alto nivel para un formulario
XmlGuiFormField.javaRepresenta un campo de formulario y contiene los metadatos para cada campo de un formulario
XmlGuiEditBox.javaImplementa un elemento de interfaz de usuario del tipo recuadro de texto
XmlGuiPickOne.javaImplementa un elemento de interfaz de usuario del tipo lista desplegable
RunForm.javaProcesa un formulario mediante la utilización de las clases mencionadas arriba
main.xmlPágina de inicio de la interfaz de usuario de la aplicación
AndroidManifest.xmlDescriptor de despliegue para la aplicación de Android
xmlgui1.xmlFormulario de muestra para recolectar registros de competición de Robotics
xmlgui1-post.phpScript PHP para procesar los envíos de formularios
xmlgui2.xmlFormulario de muestra para realizar una encuesta sobre los hábitos de mantenimiento automotriz.

La Figura 2 muestra la estructura del proyecto en Eclipse para la aplicación completa y cómo se verá al final de este tutorial. (Vea una versión solo textual de la Figura 2).

Figura 2. Proyecto en Eclipse
Screen capture of project structure in Eclipse

Si no posee un entorno de trabajo para el desarrollo de Android, este es un buen momento para instalar las herramientas de Android. Para obtener más información sobre cómo configurar un entorno de desarrollo de Android, consulte los Recursos para obtener enlaces para las dos herramientas requeridas y algunos artículos introductorios sobre cómo desarrollar aplicaciones para Android. Estar familiarizado con Android es de gran utilidad para comprender este tutorial.

Ahora que posee una visión general de la arquitectura y de la aplicación, ¡puede comenzar!


El proyecto y el modelo de datos

Ahora estamos listos para empezar el proyecto de Android en Eclipse, crear el modelo de datos y la clase que le permitirá almacenar y gestionar los metadatos para la aplicación Forms Engine.

Creación del proyecto

La creación de una aplicación de Android comienza en un lugar conocido: Abra Eclipse y seleccione File > New como se muestra en la Figura 3.

Figura 3. Creación de una nueva aplicación de Android
Screen capture of creating a new Android application

Este paso inicia el asistente para nuevos proyectos de Eclipse. Seleccione Android Project (el entorno de Java especializado para Android). Asegúrese de darle al proyecto un identificador válido (yo utilicé XMLGUI). Para que la solución coincida con la solución que se describe en este tutorial, debajo de Properties, especifique XML GUI como nombre de la aplicación y com.msi.ibm como nombre del paquete. Seleccione la casilla de verificación Create Activity y especifique XmlGui para el nombre de la actividad como se muestra en la Figura 4.

Figura 4. Configuración de un nuevo proyecto
Screen capture of setting up a new project

Una vez que haya creado el proyecto, este debe verse similar a la imagen que se muestra en la Figura 5. (Vea una versión solo textual de la Figura 5).

Figura 5. Proyecto de Android finalizado directamente desde el asistente para nuevos proyectos
Android project directly upon completion of the new project wizard

Ahora que hemos creado el proyecto, es una buena práctica comprobar que la aplicación funciona en forma segura y limpia en el emulador Android. Observe que a veces las la aplicación no se desarrolla hasta que edite y guarde el archivo de origen de Java. Esto ocasiona que las herramientas de Android SDK generen automáticamente los archivos en la carpeta gen. Esto causa que Android SDK generen automáticamente los archivos en la carpeta gen. Puede probar la aplicación si no hay entradas en la pestaña Problems o en el entorno de Eclipse.

Para probar la aplicación, cree una Configuración de ejecución como se muestra en la Figura 6. En la lista Android Application, seleccione XmlGui. Compruebe que los siguientes valores estén presentes: XmlGui en el campo Name, XMLGUI en el campo Project y com.msi.ibm.XmlGui en el campo Launch. Haga clic en Run.

Figura 6. Ejecución de la configuración de ejecución
Run Configuration setup

Ahora que el proyecto está creado, configurado y se inicia correctamente en el emulador de Android, es tiempo de crear la herramienta de recolección de datos impulsada por XML para Android.

El modelo de datos

Los elementos constitutivos de esta aplicación requieren que presente elementos de entrada para un usuario, recolecte los datos, los valide y a continuación los envíe a una ubicación específica en el servidor. Cabe señalar que esta aplicación está configurada solo para nuevos registros. No existen disposiciones para buscar un registro existente para editarlo o eliminarlo.

Para guiar la aplicación lo suficiente sobre cómo presentar los formularios de entrada de datos, se requiere un conjunto de información (conocido comúnmente como metadatos). Los metadatos son los datos sobre los datos. En términos generales, esta aplicación debe entender los siguientes elementos de datos:

  • Nombre de formulario— Nombre del formulario en lectura humana directa
  • Identificador de formulario— Identificador único para esta recolección de metadatos
  • URL de envío— El lugar al que se deben enviar los datos recolectados
  • Uno o más campos— Estos pueden ser de texto, numéricos o se pueden seleccionar de una lista de campos

Virtualmente, todos los tipos de preguntas conducen a unos de estos tres tipos de elementos de interfaz de usuario. Por ejemplo, puede implementar una casilla de verificación como un campo de elección por Sí o por No. Puede implementar una selección múltiple como campos de selección múltiple Naturalmente, es posible extender el código que se muestra en este tutorial según lo desee.

Para su aplicación, el escenario de uso es el siguiente: Se encuentra en un evento en el que se puede inscribir para una o más actividades. Puede completar una hoja de papel o puede esperar hasta que llegue a su casa y confíe en que recordará inscribirse en el sitio web de la organización para participar. En este caso, asumirá que el usuario completará un formulario simple en el momento desde su teléfono al utilizar una aplicación dinámica en un dispositivo Android, en la que proporcionará el nombre, apellido, sexo y edad del participante.

El Listado 1 muestra los contenidos de xmlgui1.xml, que representa el formulario de registro para un evento de club de Robotics.

Listado 1. xmlgui1.xml
<?xml version="1.0" encoding="utf-8"?>
<xmlgui>
<form id="1" name="Robotics Club Registration" 
   submitTo="http://serverurl/xmlgui1-post.php" >
<field name="fname" label="First Name" type="text" required="Y" options=""/>
<field name="lname" label="Last Name" type="text" required="Y" options=""/>
<field name="gender" label="Gender" type="choice" required="Y" options="Male|Female"/>
<field name="age" label="Age on 15 Oct. 2010" type="numeric" required="N" options=""/>
</form>
</xmlgui>

Observe los siguientes aspectos sobre este documento XML:

  • El XML es muy simple de analizar gracias a su uso extensivo de atributos de elementos. Este abordaje es utilizado ya que permite que la extracción de datos sea fácil que la extracción de elementos secundarios y etiquetas múltiples. El uso de atributos de esta manera también mantiene el tamaño de descarga reducido y contribuye a mantener el tiempo de análisis bajo.
  • El atributo submitTo le indica a la aplicación el lugar al que debe enviar los datos una vez que hayan sido recolectados.
  • Cada elemento field proporciona atributos para el nombre del campo y para la etiqueta. Mientras estos valores estén relacionados, conserve el valor de cada atributo name como único en relación con los demás valores de name para que la aplicación receptora pueda analizarlos y procesarlos correctamente. También debe suministrar un valor label informativo para el usuario como una indicación sobre qué clase de datos debe ir en ese campo en particular.
  • Puede aumentar el alcance de este abordaje para incluir los valores predeterminados de campo, una expresión regex para validación o un enlace para obtener más información sobre un campo determinado.
  • El campo options se utiliza como una lista delimitada para el campo choice.

Ahora que poseemos una familiaridad básica con el modelo de datos, observemos el código responsable para convertir estos datos XML en una aplicación útil.

Representación de datos

El análisis de datos es un ejercicio algo mecánico que se muestra más adelante en este tutorial. Antes de examinar el proceso de análisis, la aplicación necesita lugar para almacenar y gestionar los metadatos en la memoria. Para este fin, existen dos clases de Java a su alcance, una para el formulario y otra para representar el campo del formulario. Comience observando XmlGuiForm.java en el Listado 2.

Listado 2. XmlGuiForm.java
package com.msi.ibm;
import java.util.Vector;
import java.util.ListIterator;
import java.net.URLEncoder;

public class XmlGuiForm {

   private String formNumber;
   private String formName;
   private String submitTo;
   public Vector<XmlGuiFormField> fields;


   public XmlGuiForm()
   {
      this.fields = new Vector<XmlGuiFormField>();
      formNumber = "";
      formName = "";
      submitTo = "loopback"; // do nothing but display the results
   }
   // getters & setters
   public String getFormNumber() {
      return formNumber;
   }

   public void setFormNumber(String formNumber) {
      this.formNumber = formNumber;
   }


   public String getFormName() {
      return formName;
   }
   public void setFormName(String formName) {
      this.formName = formName;
   }

   public String getSubmitTo() {
      return submitTo;
   }

   public void setSubmitTo(String submitTo) {
      this.submitTo = submitTo;
   }

   public Vector<XmlGuiFormField> getFields() {
      return fields;
   }

   public void setFields(Vector<XmlGuiFormField> fields) {
      this.fields = fields;
   }

   public String toString()
   {
      StringBuilder sb = new StringBuilder();
      sb.append("XmlGuiForm:\n");
      sb.append("Form Number: " + this.formNumber + "\n");
      sb.append("Form Name: " + this.formName + "\n");
      sb.append("Submit To: " + this.submitTo + "\n");
      if (this.fields == null) return sb.toString();
      ListIterator<XmlGuiFormField> li = this.fields.listIterator();
      while (li.hasNext()) {
         sb.append(li.next().toString());
      }
   
      return sb.toString();
   }

   public String getFormattedResults()
   {
      StringBuilder sb = new StringBuilder();
      sb.append("Results:\n");
      if (this.fields == null) return sb.toString();
      ListIterator<XmlGuiFormField> li = this.fields.listIterator();
      while (li.hasNext()) {
         sb.append(li.next().getFormattedResult() + "\n");
      }

      return sb.toString();
   }

   public String getFormEncodedData()
   {
      try {
      int i = 0;
      StringBuilder sb = new StringBuilder();
      sb.append("Results:\n");
      if (this.fields == null) return sb.toString();
      ListIterator<XmlGuiFormField> li = this.fields.listIterator();
      while (li.hasNext()) {
         if (i != 0) sb.append("&");
         XmlGuiFormField thisField = li.next();
         sb.append(thisField.name + "=");
         String encstring = new String();
         URLEncoder.encode((String) thisField.getData(),encstring);
         sb.append(encstring);
      }

      return sb.toString();
      }
      catch (Exception e) {
         return "ErrorEncoding";
      }
   }
}

Aquí hay unos elementos importantes que observar sobre la clase XmlGuiForm.

  1. Hay cuatro variables de miembro:
    • formNumber: Este es el único identificador para el mecanismo de distribución del formulario del lado del servidor.
    • formName: Este atributo se convierte en el título del formulario, el cual provee el contexto y la confirmación para el usuario.
    • submitTo: Esta es la URL para la aplicación para enviar los datos que se introdujeron (después de haberlos validado). Este valor también puede ser un valor de retorno. En el caso de un bucle de retorno, los datos se muestran al usuario en lugar de ser enviados al servidor. Esto es de gran utilidad para fines de prueba.
    • fields: Este es una clase de vector en plantilla para mantener los datos del campo del formulario. El Listado 3 muestra los detalles para XmlGuiFormField.java.
  2. Una serie de getters y setters para cada una de estas variables.
  3. Los métodos toString() y getFormattedResults() son los responsables de generar resúmenes para lectura humana directa de la clase XmlGuiForm.
  4. El método getFormEncodedData() es utilizado al preparar los datos para enviarlos a la URL indicada en el atributo submitTo.
  5. En lugar de utilizar clases java.lang.String estrictamente concatenadas, el código emplea un StringBuilder como un medio de memoria más eficiente para construir las cadenas de datos deseadas.
  6. La clase URLEncoder es aprovechada para preparar los datos para enviarlos al servidor. Esto hace que los datos se vean como se crearon realmente mediante un formulario HTML tradicional.
  7. Algunas expansiones potenciales de esta aplicación incluyen:
    • Almacenamiento local o en caché de los metadatos para hacer que las tareas repetitivas se ejecuten más rápido.
    • Almacenamiento local para registrar los datos durante un período de tiempo antes de enviarlos.
    • Registro— de fecha y hora por GPS en cada registro con datos de ubicación.

Ahora observemos la construcción de la clase XmlGuiFormField en el Listado 3.

Listado 3. XmlGuiFormField.java
package com.msi.ibm;
// class to handle each individual form
public class XmlGuiFormField {
   String name;
   String label;
   String type;
   boolean required;
   String options;
   Object obj;   // holds the ui implementation
                   // or the EditText for example


   // getters & setters
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getLabel() {
      return label;
   }
   public void setLabel(String label) {
      this.label = label;
   }
   public String getType() {
      return type;
   }
   public void setType(String type) {
      this.type = type;
   }
   public boolean isRequired() {
      return required;
   }
   public void setRequired(boolean required) {
      this.required = required;
   }
   public String getOptions() {
      return options;
   }
   public void setOptions(String options) {
      this.options = options;
   }

   public String toString()
   {
      StringBuilder sb = new StringBuilder();
      sb.append("Field Name: " + this.name + "\n");
      sb.append("Field Label: " + this.label + "\n");
      sb.append("Field Type: " + this.type + "\n");
      sb.append("Required? : " + this.required + "\n");
      sb.append("Options : " + this.options + "\n");
      sb.append("Value : " + (String) this.getData() + "\n");

      return sb.toString();
   }
   public String getFormattedResult()
   {
      return this.name + "= [" + (String) this.getData() + "]";

   }

   public Object getData()
   {
      if (type.equals("text") || type.equals("numeric"))
      {
         if (obj != null) {
            XmlGuiEditBox b = (XmlGuiEditBox) obj;
            return b.getValue();
         }
      }
      if (type.equals("choice")) {
         if (obj != null) {
            XmlGuiPickOne po = (XmlGuiPickOne) obj;
            return po.getValue();
         }
      }

      // You could add logic for other UI elements here
      return null;
   }

}

Observe con mayor detenimiento la clase XmlGuiFormField.

  • Hay seis miembros de nivel de clase:
    1. name contiene el nombre del campo— este es el nombre del campo del valor de los datos, análogo al nombre del campo de un formulario en HTML o el nombre de columna de base de datos.
    2. label contiene una descripción del campo o el valor que se muestra al usuario.
    3. type indica el tipo de campo de la interfaz de usuario que se desea construir.
      • text significa que este campo se implementa con un campo EditText para entradas alfanuméricas. Este es el valor más común.
      • numeric también es un valor EditText, pero solo está limitado a un valor de entrada numérica.
      • choice hace que el campo sea una lista desplegable.
    4. required es un valor booleano que marca el campo como requerido o no. Si el campo se requiere pero no se completa, se muestra un mensaje de error al usuario cuando este intenta enviar el formulario.
    5. options es un valor de cadena utilizado para transmitir la lista de selecciones disponibles para un campo de elección. Este campo está disponible para otros campos para ser utilizado quizás como una expresión regex para validación o puede ser sustituido para especificar un valor predeterminado.
    6. obj representa el widget de la interfaz de usuario. Por ejemplo, esta variable contiene un EditText para un campo numérico o de texto. Para un campo de elección, el miembro obj contiene un widget spinner. Este abordaje se explica con mayor detalle más adelante en este tutorial.
  • Como se preveía, estas variables tienen un número de getters y setters.
  • Los métodos toString() y getFormattedResult() aprovechan el método getData() que se explica a continuación.
  • En la clase XmlGuiFormField, es necesario gestionar más de un tipo de datos por lo que el código necesita ser explícito sobre cómo almacenar y acceder a los datos. El método getData() examina el tipo de cambio y realiza una conversión de tipo en el campo obj para interactuar correctamente con el objeto almacenado. Si desea agregar nuevos tipos de campos a esta a infraestructura, puede expandir el método getData() para que este sea compatible con el nuevo tipo de campo (vea el comentario cerca del final del Listado 3.

Ahora posee un método para almacenar y gestionar los metadatos. Es tiempo de ver a la aplicación en acción y después comenzar a atar las piezas entre sí.


Ensamblado de una interfaz de usuario

Comience por crear un formulario para que un usuario móvil introduzca los datos.

Obtención de los datos desde los niveles más altos

El punto de entrada de la aplicación reside en XmlGui.java, como se muestra en el Listado 4.

Listado 4. El punto de entrada de la aplicación: XmlGui
package com.msi.ibm;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Button;
import android.widget.TextView;
import android.content.Intent;
import android.util.Log;
public class XmlGui extends Activity {
        final String tag = XmlGui.class.getName();
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        Button btnRunForm = (Button) this.findViewById(R.id.btnRunForm);
        btnRunForm.setOnClickListener(new Button.OnClickListener()
        {
           public void onClick(View v)
           {
                       EditText formNumber = (EditText) findViewById(R.id.formNumber);
                       Log.i(tag,"Attempting to process Form # 
                   [" + formNumber.getText().toString() + "]");
                       Intent newFormInfo = new Intent(XmlGui.this,RunForm.class);
                       newFormInfo.putExtra("formNumber", 
                   formNumber.getText().toString());
                       startActivity(newFormInfo);
           }
        });
    }
}

La interfaz de usuario para Activity principal es muy simple; consiste solo en:

  • Una etiqueta (TextView)
  • Un cuadro de entrada (EditText)
  • Un botón (Button)

La naturaleza del código para XmlGui Activity es estándar. Debe inflar un layout creado durante el momento de diseño y luego definir y crear un manejador de botones para implementar la funcionalidad deseada (esta se explica más adelante).

La interfaz de usuario la define en el archivo main.xml (que se encuentra en la subcarpeta de diseño en la carpeta res). El Listado 5 muestra el archivo main.xml.

Listado 5. main.xml
<?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="wrap_content"
    >

   <TextView  
       android:layout_width="wrap_content" 
       android:layout_height="wrap_content" 
       android:text="@string/Title"
       />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >

   <EditText
       android:layout_width="100px"
       android:layout_height="wrap_content"
       android:text="1"
       android:id="@+id/formNumber"
   android:numeric="integer"/>
   <Button android:text="Run Form" android:id="@+id/btnRunForm" 
       android:layout_width="wrap_content" 
       android:layout_height="wrap_content">
   </Button>
</LinearLayout>

</LinearLayout>

Como recordatorio, puede editar directamente el XML para modificar los diseños o utilizar la herramienta Layout en las herramientas de desarrollo de Android como se muestra en la Figura 7.

Figura 7. Herramienta Layout
Screen capture of Layout tool

Para ejecutar la aplicación, haga clic en el icono de la pantalla de inicio que inicia XmlGui Activity que se muestra en la Figura 8.

Figura 8. Aplicación en acción
Screen capture of application in action

Cuando el usuario introduce un número de formulario y hace clic en el botón Run Form, estas acciones inician una serie de eventos. Examinemos el método onClick() línea por línea. Recuerde que el método onClick() está en la clase XmlGui en el Listado 4.

Obtiene una referencia para el campo EditText denominado formNumber. La enumeración R.id.formNumber es generada automáticamente por las herramientas de compilación de Android siempre que se guarde el archivo main.xml:

EditText formNumber = (EditText) findViewById(R.id.formNumber);

A continuación, coloque una línea en el registro. Puede observar la salida de este registro en la herramienta DDMS (Dalvik Debug Monitor Service) en Eclipse incluida en el plug-in de herramientas de desarrollo de Android:

Log.i(tag,"Attempting to process Form # [" + formNumber.getText().toString() + "]");

La clase RunForm proporciona la implementación real de Form Engine, que extiende la clase Activity. Para iniciar Activity, cree un Intent que identifique explícitamente la clase RunForm class:

Intent newFormInfo = new Intent(XmlGui.this,RunForm.class);

No solo deseará iniciar la actividad RunForm Activity, también deseará especificar qué formulario presentar. Para hacer esto, agregue el número de formulario a Intent a través del método putExtra:

newFormInfo.putExtra("formNumber", formNumber.getText().toString());

Este valor es extraído por la clase RunForm, que se muestra más adelante.

Una vez configurado Intent, inicie Activity con una llamada a startActivity:

startActivity(newFormInfo);

Con su aplicación, el usuario introducirá el número de formulario y hará clic en el botón Run Form. Esto desencadenará la serie de eventos descrita anteriormente, lo que causará que la clase RunForm procese la solicitud. La introducción de un número de formulario es realmente solo una herramienta de prueba para los objetivos de este tutorial. Existen otros medios por los cuales este evento desencadenador puede tener lugar. Ejemplos más prácticos incluyen enlaces personalizados desde una página web, un mensaje enviado a través del servicio de mensajes cortos (SMS), un desencadenador basado en la ubicación según la proximidad o incluso un código QR escaneado (código de respuesta rápida).

Ejecución del formulario

La clase RunForm es la encargada de dirigir la aplicación. Esta clase se ejecuta con un número de formulario para procesar. Ahora examinemos el método SubmitForm() en el Listado 6.

Listado 6. El método onCreate()
public class RunForm extends Activity {
    /** Called when the activity is first created. */
           String tag = RunForm.class.getName();
           XmlGuiForm theForm;
           ProgressDialog progressDialog;
           Handler progressHandler;

           @Override
    public void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           String formNumber = "";
           Intent startingIntent = getIntent();
           if(startingIntent == null) {
              Log.e(tag,"No Intent?  We're not supposed to be here...");
              finish();
              return;
           }
              formNumber = startingIntent.getStringExtra("formNumber");
              Log.i(tag,"Running Form [" + formNumber + "]");
              if (GetFormData(formNumber)) {
                     DisplayForm();
          }
          else
          {
                  Log.e(tag,"Couldn't parse the Form.");
                  AlertDialog.Builder bd = new AlertDialog.Builder(this);
                  AlertDialog ad = bd.create();
                  ad.setTitle("Error");
                  ad.setMessage("Could not parse the Form data");
                  ad.show();

          }
    }
         // other methods omitted and shown later
}

Como se puede observar en el Listado 6, primero debe extraer formNumber de Intent que desencadenó Activity. Sin un número de formulario para procesar, Activity no tiene ninguna acción para realizar.

Una vez que haya extraído el valor, el siguiente requisito es establecer una conexión con el servidor para descargar las especificaciones del formulario. (Observe que una mejora para este enfoque puede ser una observación de los metadatos en la memoria caché local antes de capturar los datos.) El Listado 7 muestra el método GetFormData().

Listado 7. El método GetFormData()
   private boolean GetFormData(String formNumber) {
   try {
      Log.i(tag,"ProcessForm");
      URL url = new URL("http://www.example.com/xmlgui" + formNumber + ".xml");
      Log.i(tag,url.toString());
      InputStream is = url.openConnection().getInputStream();
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = factory.newDocumentBuilder();
      Document dom = db.parse(is);
      Element root = dom.getDocumentElement();
      NodeList forms = root.getElementsByTagName("form");
      if (forms.getLength() < 1) {
         // nothing here??
         Log.e(tag,"No form, let's bail");
         return false;
      }
      Node form = forms.item(0);
      theForm = new XmlGuiForm();

      // process form level
      NamedNodeMap map = form.getAttributes();
      theForm.setFormNumber(map.getNamedItem("id").getNodeValue());
      theForm.setFormName(map.getNamedItem("name").getNodeValue());
      if (map.getNamedItem("submitTo") != null)
         theForm.setSubmitTo(map.getNamedItem("submitTo").getNodeValue());
      else
         theForm.setSubmitTo("loopback");

      // now process the fields
      NodeList fields = root.getElementsByTagName("field");
      for (int i=0;i<fields.getLength();i++) {
         Node fieldNode = fields.item(i);
         NamedNodeMap attr = fieldNode.getAttributes();
         XmlGuiFormField tempField =  new XmlGuiFormField();
         tempField.setName(attr.getNamedItem("name").getNodeValue());
         tempField.setLabel(attr.getNamedItem("label").getNodeValue());
         tempField.setType(attr.getNamedItem("type").getNodeValue());
         if (attr.getNamedItem("required").getNodeValue().equals("Y"))
            tempField.setRequired(true);
         else
            tempField.setRequired(false);
         tempField.setOptions(attr.getNamedItem("options").getNodeValue());
         theForm.getFields().add(tempField);
      }

      Log.i(tag,theForm.toString());
      return true;
   } catch (Exception e) {
      Log.e(tag,"Error occurred in ProcessForm:" + e.getMessage());
      e.printStackTrace();
      return false;
   }
}

Este código es responsable de obtener datos de un repositorio de metadatos, en este caso mediante la descarga de un archivo XML de un servidor web:

URL url = new URL("http://10.211.55.2/~fableson/xmlgui" + formNumber + ".xml");

Manipule los datos del archivo XML a través de un analizador DOM, para extraer el formulario y los elementos del campo y los atributos para almacenarlos en instancias de las clases XmlGuiForm y XmlGuiFormField respectivamente. La mayor parte de este método está dedicado al análisis y rellenado de tareas.

Dos enfoques para el análisis de archivos XML son DOM y SAX. El funcionamiento del analizador DOM está basado en analizar un documento dentro de la memoria para que después la aplicación recorra un árbol de modelo de objetos de documento para acceder a diversos elementos de los datos contenidos en el archivo XML. También puede utilizar aquí el modelo del analizador SAX ya que usted genera su propia representación del documento al completar las dos clases.

La ventaja del enfoque DOM para la aplicación es que esta contiene varios procedimientos y es fácil seguir el código mientras que el enfoque SAX requiere funciones de devolución de llamadas en las que solo se almacenan los datos deseados. En algunas instancias, la complejidad del código escrito por el desarrollador para implementar las funciones de devolución de llamadas de SAX pueden ser notablemente más elevadas que el enfoque DOM. Debido a que los datos del archivo XML son analizados completamente en el enfoque DOM, este requiere una actividad más intensiva para la memoria. Para los objetivos en esta aplicación, la simplicidad y facilidad de DOM fue un impulsor más grande que la gestión de memoria ya que el formulario de metadatos es bastante pequeño.

Consulte los Recursos para obtener una excelente referencia sobre cómo codificar analizadores XML en Android.

Hemos transformado los formularios de metadatos del archivo XML en instancias de la clase Java. Ahora es momento de presentar el formulario para reunir datos del usuario.


Recolección de datos del usuario

Ahora que ha creado el diseño principal de pantalla Activity, puede crear los formularios de la interfaz de usuario para recolectar los datos. Aquí crearemos un formulario de inscripción del Club Robotics y un formulario de encuesta sobre automantenimiento.

Uso de los metadatos

Esta aplicación se articula en la habilidad de los programadores de Android para manipular dinámicamente la interfaz de usuario. A comienzos de este tutorial, hemos examinado el archivo main.xml, el archivo que define el diseño de pantalla de la clase XmlGui (el elemento Activity principal). Esta aplicación sería virtualmente imposible en su forma actual si tuviera que definir siempre los elementos de la interfaz de usuario durante el tiempo de diseño o de compilación.

Afortunadamente, no tenemos estos límites para condicionarnos. El método DisplayForm() es responsable de convertir los metadatos en elementos de la interfaz de usuario para los fines de recolección de datos. El código está dividido básicamente en dos áreas funcionales principales: el diseño de los elementos de la interfaz de usuario y la manipulación del botón de envío.

Examinemos en primer lugar la lógica de diseño: Este es el código que convierte el objeto XmlGuiForm en un formulario real en la pantalla. El Listado 8 muestra este código.

Listado 8. La lógica de diseño
    private boolean DisplayForm()
    {

        try
        {
            ScrollView sv = new ScrollView(this);

        final LinearLayout ll = new LinearLayout(this);
        sv.addView(ll);
        ll.setOrientation(android.widget.LinearLayout.VERTICAL);

        // walk through the form elements and dynamically create them, 
        // leveraging the mini library of tools.
        int i;
        for (i=0;i<theForm.fields.size();i++) {
            if (theForm.fields.elementAt(i).getType().equals("text")) {
                    theForm.fields.elementAt(i).obj = new
                    XmlGuiEditBox(this,(theForm.fields.elementAt(i).isRequired()
                    ? "*" : "") + theForm.fields.elementAt(i).getLabel(),"");
                    ll.addView((View) theForm.fields.elementAt(i).obj);
            }
            if (theForm.fields.elementAt(i).getType().equals("numeric")) {
                    theForm.fields.elementAt(i).obj = new
                    XmlGuiEditBox(this,(theForm.fields.elementAt(i).isRequired() 
                    ? "*" : "") + theForm.fields.elementAt(i).getLabel(),"");
                    ((XmlGuiEditBox)theForm.fields.elementAt(i).obj).makeNumeric();
                    ll.addView((View) theForm.fields.elementAt(i).obj);
            }
            if (theForm.fields.elementAt(i).getType().equals("choice")) {
                    theForm.fields.elementAt(i).obj = new
                    XmlGuiPickOne(this,(theForm.fields.elementAt(i).isRequired()
                    ? "*" : "") + theForm.fields.elementAt(i).getLabel(), 
                    theForm.fields.elementAt(i).getOptions());
                    ll.addView((View) theForm.fields.elementAt(i).obj);
            }
        }


        Button btn = new Button(this);
        btn.setLayoutParams(new LayoutParams
        (ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.
        WRAP_CONTENT));

        ll.addView(btn);

        btn.setText("Submit");
        btn.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                // check if this form is Valid
                if (!CheckForm())
                {
                    AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
                AlertDialog ad = bd.create();
                ad.setTitle("Error");
                ad.setMessage("Please enter all required (*) fields");
                ad.show();
                return;

                }
                if (theForm.getSubmitTo().equals("loopback")) {
                    // just display the results to the screen
                    String formResults = theForm.getFormattedResults();
                    Log.i(tag,formResults);
                    AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
                AlertDialog ad = bd.create();
                ad.setTitle("Results");
                ad.setMessage(formResults);
                ad.show();
                return;

                } else {
                    if (!SubmitForm()) {
                        AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
                    AlertDialog ad = bd.create();
                    ad.setTitle("Error");
                    ad.setMessage("Error submitting form");
                    ad.show();
                    return;
                    }
                }

            }
        } );

        setContentView(sv);
        setTitle(theForm.getFormName());

        return true;

        } catch (Exception e) {
            Log.e(tag,"Error Displaying Form");
            return false;
        }
    }

Debe anticipar la disponibilidad de más campos que puedan ajustarse a una sola pantalla; utilice ScrollView como una vista primaria o contenedora. Con este ScrollView, utiliza un diseño LinearLayout para organizar los diversos widgets de la interfaz de usuario en una columna vertical.

Este enfoque es bastante sencillo:

  • A través de la lista de XmlGuiFormField, enumera los objetos contenidos dentro del miembro fields de la instancia XmlGuiForm.
  • Según el tipo de campo solicitado, un elemento diferente de la interfaz de usuario se instancia y se agrega a LinearLayout. Examinaremos los diferentes widgets de la interfaz de usuario por un momento.

Una vez que haya creado y agregado los elementos de la interfaz de usuario al diseño lineal, asigne la instancia ScrollView completa al contenido de esta pantalla y el nombre del formulario como el título de la pantalla. La Figura 9 muestra la pantalla de inscripción del Club Robotics lista para que el usuario introduzca la información. Este formulario es el resultado del proceso de los datos del archivo XML que se encuentran en el Listado 1.

Figura 9. Formulario de inscripción de Robotics en acción
Screen capture of robotics registration form in action

Veamos los diferentes widgets personalizados de la interfaz de usuario creados para esta aplicación.

Recuerde que esta aplicación tiene definidos tres tipos de campos de entrada de datos: Estos tres tipos de campo se implementan mediante dos widgets personalizados diferentes: XmlGuiEditBox y XmlGuiPickOne.

Los valores numéricos y de texto presentan grandes similitudes y puede aprovechar el mismo enfoque de EditView para ambos pero con diferentes filtros de entrada para alternar entre valores alfanuméricos o solo valores numéricos. El Listado 9 muestra el código para la clase XmlGuiEditBox.

Listado 9. La clase XmlGuiEditBox
package com.msi.ibm;

import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.EditText;
import android.text.method.DigitsKeyListener;

public class XmlGuiEditBox extends LinearLayout {
   TextView label;
   EditText txtBox;

   public XmlGuiEditBox(Context context,String labelText,String initialText) {
      super(context);
      label = new TextView(context);
      label.setText(labelText);
      txtBox = new EditText(context);
      txtBox.setText(initialText);
      txtBox.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams
                   .FILL_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT));
      this.addView(label);
      this.addView(txtBox);
   }

   public XmlGuiEditBox(Context context, AttributeSet attrs) {
      super(context, attrs);
      // TODO Auto-generated constructor stub
   }

   public void makeNumeric()
   {
      DigitsKeyListener dkl = new DigitsKeyListener(true,true);
      txtBox.setKeyListener(dkl);
   }
   public String getValue()
   {
      return txtBox.getText().toString();
   }
   public void setValue(String v)
   {
      txtBox.setText(v);
   }
}

La clase XmlGuiEditBox extiende la clase LinearLayout y contiene una etiqueta textual para describir la entrada solicitada y un elemento EditText para recolectar los datos introducidos. Toda la inicialización de los objetos se realiza en el constructor. Esta puede considerarse como una mala práctica pero este es un ejercicio que puede realizar si no está a gusto con ese enfoque.

Existen otros tres métodos para analizar. Los métodos getValue() y setValue() que realizan las actividades que tiene en mente. Estos son getter y setter para interactuar con el campo EditText.

makeNumeric() es el tercer método y solo se recurre a este al configurar un campo de formulario numérico. Se emplea una instancia de DigitsKeyListener para filtrar todas las claves que no sean numéricas. Por otra parte, otro componente que recibirá gratuitamente es un teclado apropiado que se muestra según el tipo de XmlGuiEditBox que esté en uso — con o sin la configuración numérica.

La Figura 10 muestra el formulario en acción con un teclado alfabético ya que el campo Apellido está configurado para entradas alfabéticas, en otras palabras text.

Figura 10. Entrada de clave alfanumérica
Screen capture of alphanumeric key entry

La Figura 11 muestra el teclado numérico en uso ya que el campo edad está configurado para datos del tipo numeric.

Figura 11. Teclado numérico
Screen capture of numeric keyboard

El campo de selección, implementado en la interfaz de usuario a través de la clase XmlGuiPickOne presenta algunas diferencias. El campo de selección es implementado como un widget Spinner de Android. Esta interfaz de usuario es similar a un cuadro de lista desplegable en otros entornos de programación, en donde el usuario debe seleccionar una de las opciones de selección existentes. La Figura 12 muestra tres instancias del widget XmlGuiPickOne.

Figura 12. Encuesta de automantenimiento con tres instancias XmlGuiPickOne
Screen capture of auto maintenance survey with three XmlGuiPickOne instances

En este ejemplo, los datos recolectados se utilizarán para fines estadísticos por lo que la normalización de las entradas posibles hace que el procesamiento de los datos sea más limpio que tratar con campos de entrada de texto libre. Por supuesto, puede definir el campo Estado como un campo de elección si desea limitar la encuesta a una región geográfica particular.

El Listado 10 muestra el código para la clase XmlGuiPickOne.

Listado 10. La clase XmlGuiPickOne
package com.msi.ibm;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Spinner;
import android.widget.ArrayAdapter;

public class XmlGuiPickOne extends LinearLayout {
   String tag = XmlGuiPickOne.class.getName();
   TextView label;
   ArrayAdapter<String> aa;
   Spinner spinner;

   public XmlGuiPickOne(Context context,String labelText,String options) {
      super(context);
      label = new TextView(context);
      label.setText(labelText);
      spinner = new Spinner(context);
      String []opts = options.split("\\|");
      aa = new ArrayAdapter<String>( context,
           android.R.layout.simple_spinner_item,opts);
      spinner.setAdapter(aa);
      this.addView(label);
      this.addView(spinner);
   }

   public XmlGuiPickOne(Context context, AttributeSet attrs) {
      super(context, attrs);
      // TODO Auto-generated constructor stub
   }


   public String getValue()
   {
      return (String) spinner.getSelectedItem().toString();
   }

}

Esta clase es muy similar a la clase XmlGuiEditBox. La diferencia principal es que se emplea un control Spinner en lugar de un control EditText. Además, observe que esta clase solo implementa el método getValue(). Una mejora evidente para esta clase es permitir al usuario especificar un valor predeterminado.

Observe el uso del miembro options para rellenar la lista de selecciones. En este código, la cadena que contiene las opciones disponibles se divide en un conjunto mediante el uso de la expresión regex y luego pasa a una instancia de un ArrayAdapter. La constante android.R.layout.simple_spinner_item está integrada para Android. Esta no se suministraba en el código de la aplicación del tutorial. Una vez que el adaptador haya sido configurado, asígnelo al elemento Spinner. La Figura 13 presenta la lista de selecciones que se muestra en la pantalla, que le indican al usuario la cantidad de millas entre un cambio de aceite y otro.

Figura 13. XmlGuiPickOne preguntando por los cambios de aceite
Screen capture of XmlGuiPickOne asking about oil changes with choices in mile intervals

Ahora que el usuario puede introducir datos en el formulario, es momento de validar y enviar los datos.


Guardado y envío de datos

Ahora debe crear un recurso para que el usuario pueda guardar los datos para validarlos y enviarlos a un servidor.

Guardado de los datos

Es hora de volver al método DisplayForm() de la clase RunForm. Recuerde que la primera opción de este método es la encargada de traer el formulario. A continuación, examinará el manejador onClick() del botón de envío, en el Listado 11.

Listado 11. El manejador onClick()
btn.setOnClickListener(new Button.OnClickListener() {
   public void onClick(View v) {
       // check if this form is Valid
       if (!CheckForm())
       {
           AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
               AlertDialog ad = bd.create();
               ad.setTitle("Error");
               ad.setMessage("Please enter all required (*) fields");
               ad.show();
               return;
       }
       if (theForm.getSubmitTo().equals("loopback")) {
           // just display the results to the screen
           String formResults = theForm.getFormattedResults();
           Log.i(tag,formResults);
           AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
           AlertDialog ad = bd.create();
               ad.setTitle("Results");
               ad.setMessage(formResults);
               ad.show();
               return;
       } else {
           if (!SubmitForm()) {
               AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
           AlertDialog ad = bd.create();
           ad.setTitle("Error");
           ad.setMessage("Error submitting form");
           ad.show();
           return;
           }
       }
   }
} );

Cuando el usuario selecciona el botón de envío, las entradas del formulario son verificadas para comprobar que todos los campos requeridos hayan sido completados. De lo contrario, un AlertDialog le recordará al usuario que debe completar todos los campos. En el caso supuesto de que los datos hayan sido introducidos de manera satisfactoria, es momento de enviarlos.

El proceso de envío de los datos consta en uno de dos campos para la aplicación de este tutorial. Si el campo submitTo del formulario ha sido configurado para el bucle de retorno, los valores se reproducen simplemente en la pantalla. Esto es de gran utilidad para fines de prueba. Una vez que la herramienta del formulario recolecte los datos correctamente y usted esté conforme, es momento de apuntar a una página de servidor para registrar las entradas.

El Listado 12 muestra el método CheckForm(). Este código es bastante directo y conciso. Cada campo es verificado para comprobar si este es requerido. Si el campo es requerido pero el usuario no proporcionó la información, se establece una bandera. Puede mejorar esto para proveer un comentario más específico al usuario.

Listado 12. El método CheckForm()
private boolean CheckForm()
{
    try {
       int i;
       boolean good = true;


       for (i=0;i<theForm.fields.size();i++) {
                   String fieldValue = (String)
               theForm.fields.elementAt(i).getData();
                   Log.i(tag,theForm.fields.elementAt(i)
               .getName() + " is [" + fieldValue + "]");
                   if (theForm.fields.elementAt(i).isRequired()) {
                       if (fieldValue == null) {
                           good = false;
                       } else {
                           if (fieldValue.trim().length() == 0) {
                               good = false;
                           }
                       }

                   }
       }
       return good;
    } catch(Exception e) {
       Log.e(tag,"Error in CheckForm()::" + e.getMessage());
       e.printStackTrace();
       return false;
    }
}

Ahora, es tiempo de enviar los datos recolectados al servidor. Examine el método SubmitForm() en el Listado 13.

Listado 13. El método SubmitForm()
private boolean SubmitForm()
{
       try {
                   boolean ok = true;
       this.progressDialog = ProgressDialog.show(this, 
         theForm.getFormName(), "Saving Form Data", true,false);
       this.progressHandler = new Handler() {

               @Override
               public void handleMessage(Message msg) {
                   // process incoming messages here
                   switch (msg.what) {
                       case 0:
                           // update progress bar
                           progressDialog.setMessage("" + (String) msg.obj);
                           break;
                       case 1:
                           progressDialog.cancel();
                           finish();
                           break;
                       case 2:
                               progressDialog.cancel();
                               break;
                   }
                   super.handleMessage(msg);
               }

       };

       Thread workthread = new Thread(new TransmitFormData(theForm));

       workthread.start();

               return ok;
       } catch (Exception e) {
               Log.e(tag,"Error in SubmitForm()::" + e.getMessage());
               e.printStackTrace();
       // tell user that the submission failed....
       Message msg = new Message();
       msg.what = 1;
       this.progressHandler.sendMessage(msg);

               return false;
       }

}

En primer lugar, debe configurar una instancia de la clase android.os.Handler. La clase Handler es de gran utilidad cuando la aplicación necesita compartir datos con diferentes subprocesos. Otro aspecto importante para observar en el método SubmitForm() es el uso de un ProgressDialog. Observe que ProgressDialog y Handler están definidos como variables de nivel de clase en el Listado 14.

Listado 14. El ProgressDialog y el manejador
public class RunForm extends Activity {
    /** Called when the activity is first created. */
      String tag = RunForm.class.getName();
      XmlGuiForm theForm;
      ProgressDialog progressDialog;
      Handler progressHandler;
     ...
}

No desea bloquear innecesariamente la aplicación mientras establece la comunicación con el servidor; para ello, utiliza un subproceso de segundo plano para la comunicación, pero confía en el manejador para recibir notificaciones del subproceso de comunicaciones para proporcionar respuestas al usuario. Observe que se supone que solo el subproceso principal debe interactuar con la interfaz de usuario. Una alternativa para el enfoque de procesos separado es la clase AsyncTask que se encuentra en el paquete del sistema operativo de Android.

A medida que la aplicación se conecta al servidor para transferir los datos, esta tiene la oportunidad de informar al usuario el estado de la operación, lo que por supuesto, es una buena práctica. La Figura 14 muestra la variable ProgressDialog en acción.

Figura 14. El ProgressDialog
Screen capture of The ProgressDialog

El código real de comunicaciones del servidor se encuentra en el Listado 15, en la clase TransmitFormData(), que implementa la interfaz Runnable.

Listado 15. La clase TransmitFormData
   private class TransmitFormData implements Runnable
   {
   XmlGuiForm _form;
   Message msg;
   TransmitFormData(XmlGuiForm form) {
       this._form = form;
   }

   public void run() {

       try { 
                msg = new Message();
                msg.what = 0;
                msg.obj = ("Connecting to Server");
                progressHandler.sendMessage(msg);

                URL url = new URL(_form.getSubmitTo());
                URLConnection conn = url.openConnection();
                conn.setDoOutput(true);
                BufferedOutputStream wr = new BufferedOutputStream
                       (conn.getOutputStream());
                String data = _form.getFormEncodedData();
                wr.write(data.getBytes());
                wr.flush();
                wr.close();
 
                msg = new Message();
                msg.what = 0;
                msg.obj = ("Data Sent");
                progressHandler.sendMessage(msg);

                // Get the response
                BufferedReader rd = new BufferedReader(new
                 InputStreamReader(conn.getInputStream()));
                String line = "";
                Boolean bSuccess = false;
                while ((line = rd.readLine()) != null) {
                       if (line.indexOf("SUCCESS") != -1) {
                           bSuccess = true;
                       }
                       // Process line...
                       Log.v(tag, line);
                }
                wr.close();
                rd.close();

                if (bSuccess) {
                       msg = new Message();
                       msg.what = 0;
                       msg.obj = ("Form Submitted Successfully");
                       progressHandler.sendMessage(msg);

                       msg = new Message();
                       msg.what = 1;
                       progressHandler.sendMessage(msg);
                       return;

                }
       } catch (Exception e) {
                Log.d(tag, "Failed to send form data: " + e.getMessage());
                msg = new Message();
                msg.what = 0;
                msg.obj = ("Error Sending Form Data");
                progressHandler.sendMessage(msg);
       }
       msg = new Message();
       msg.what = 2;
       progressHandler.sendMessage(msg);
   }

   }

La clase TransmitFormData es responsable de la conexión al servidor indicada en el miembro submitTo de la instancia XmlGuiForm (como se obtuvo de los metadatos). Esta actualiza periódicamente el subproceso principal de la aplicación al enviar una instancia de la clase Message al manipulador a través del método sendMessage(). Dos miembros se completan en la clase Message:

  • El valor what actúa como un mecanismo de conmutación de alto nivel que informa al manipulador qué acción debe realizar con ese mensaje.
  • El valor obj especifica un objeto java.lang.Object opcional. En este caso, se aprueba y utiliza una instancia java.lang.String para mostrarla en el diálogo de progreso.

El esquema utilizado por cualquier aplicación determinada es arbitrario. Esta aplicación utiliza los valores en la Tabla 3.

Tabla 3. Los valores de la aplicación permitidos para what
ValorComentario
0Obj contiene una cadena de texto para mostrar al usuario.
1Finalización satisfactoria de la transmisión, ¡ha terminado!
2Se produjo un error. Informe al usuario que algo está mal y no elimine los datos.

La Figura 15 muestra el mensaje final en ProgressDialog después de una transmisión correcta de datos del formulario.

Figura 15. Presentación del formulario
Screen capture of form submission message

Una vez que el formulario haya sido enviado correctamente, la aplicación regresa a la página principal. Para una aplicación preparada para producción, lo que suceda a continuación depende en buena medida de los motivos de la organización para reunir los datos. La pantalla se puede restablecer simplemente para tomar otra entrada, como en una aplicación de inventario físico. O quizás pueda dirigir al usuario a otra pantalla.

Para que la aplicación se ejecute correctamente, el archivo AndroidManifest.xml debe contener referencias sobre todas las clases utilizadas de Activity y debe incluir el permiso de usuario para acceder a Internet. El Listado 16 muestra el archivo AndroidManifest.xml para la aplicación del tutorial.

Listado 16. El archivo AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.msi.ibm"
      android:versionCode="1"
      android:versionName="1.0">
      <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".XmlGui"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".RunForm">
        </activity>
    </application>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>

</manifest>

Antes de finalizar, examine brevemente el script del lado del servidor.


Proporcione un script del lado del servidor

Para los fines de este tutorial, utilizará un script PHP para reunir los datos requeridos y agregarlos a un archivo de texto.

En el servidor

Lo que sucede exactamente en el servidor depende de la organización que reúne los datos. Un enfoque común para la recolección de datos es almacenar los datos de los formularios en una base de datos relacional tal como DB2®, MySQL, SQL Server, Oracle, etc. Una vez que los datos estén almacenados en la base de datos, estos se pueden tomar en parte o en conjunto y analizarlos.

Para este tutorial, los datos son recolectados por un script PHP y son agregados a un archivo de texto. El Listado 17 muestra el formulario PHP asociado con el formulario de registro de Robotics.

Listado 17. El formulario PHP de Robotics
<?php
// xmlgui form # 1
// this page is expecting
// fname
// lname
// gender
// age


$filename = "/pathtowritablefile/datafile.txt";


$f = fopen($filename,"a");
fprintf($f,"Data received @ ".date(DATE_RFC822));
fprintf($f,"\n");
fprintf($f,'First Name:['.$_POST['fname'].']');
fprintf($f,"\n");
fprintf($f,'Last Name:['.$_POST['lname'].']');
fprintf($f,"\n");
fprintf($f,'Gender:['.$_POST['gender'].']');
fprintf($f,"\n");
fprintf($f,'Age:['.$_POST['age'].']');
fprintf($f,"\n");
fclose($f);
print "SUCCESS";
?>

Si el script devuelve la cadena SUCCESS, la clase RunForm se restablecerá. Cualquier otro valor generará un mensaje de error que recibirá el usuario y le permitirá corregir las entradas o, de lo contrario, obtener ayuda mediante el envío del formulario.


Conclusión

Este tutorial presentó una infraestructura para servir preguntas dinámicas para un usuario de Android basado en un enfoque de una aplicación nativa que utiliza Android SDK. Ya hemos visto la presentación de formularios dinámicos, técnicas de validación y procesamiento y comunicaciones de la aplicación con el servidor web.


Descargar

DescripciónNombretamaño
Forms engine source codeformsengine.zip78KB

Recursos

Aprender

Obtener los productos y tecnologías

Comentar

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Desarrollo móvil, Industries
ArticleID=859240
ArticleTitle=Desarrollo de interfaces dinámicas de usuario con Android y XML
publish-date=02252013