«

»

sep 04 2012

Persistencia de aplicaciones con ADA Framework – Parte 2

 
Hace tiempo os mostramos en una entrada cómo utilizar esta librería para manejar la persistencia en nuestras aplicaciones. En esta entrada vamos a ver algunos conceptos avanzados de la librería.

Para empezar, debes realizar los pasos 1, 2 y 3 de la siguiente entrada:

Una vez que tenemos el proyecto preparado vamos a ver las tres principales características que diferencian a ADA Framework del resto de librerías de gestión de datos.

 

1. Relleno Automático de Adapters

La primera característica que vamos a ver es el relleno automático de adapters. Como su nombre indica, esta característica nos permite abstraernos de tratar los datos dentro del adapter, simplemente utilizaremos un método del ObjectSet para indicarle que rellene el adapter con los datos del conjunto.

La idea es tener un resultado como el siguiente:

 

 

Para ello utilizaremos el layout de cada elemento del primer artículo de ADA Framework y que podemos ver a continuación:
 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/name"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_margin="5dp"
        style="@android:style/TextAppearance.DeviceDefault.Medium"/>

    <TextView
        android:id="@+id/category"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_margin="5dp"
        style="@android:style/TextAppearance.DeviceDefault.Small"/>

</LinearLayout>

 

Lo siguiente que tenemos que hacer es crear el adapter. Para hacer uso de esta característica de ADA Framework tenemos que utilizar un ArrayAdapter. Como en nuestro ejemplo utilizamos un array de objetos (Productos) necesitamos extender de la clase ArrayAdapter y definir cómo se mostrarán cada uno de los elementos.

A continuación el código de nuestro adapter:
 

class MyAdapter extends ArrayAdapter<Product> {

    private int resource;

    public MyAdapter(Context context, int resource) {
        super(context, resource);
        this.resource = resource;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View itemView = convertView;
        if (itemView == null) {
            itemView = inflater.inflate(resource, null);
        }
        Product product = (Product) getItem(position);
        ((TextView) itemView.findViewById(R.id.name)).setText(product.getName());
        if (product.getCategory() == null) {
            ((TextView) itemView.findViewById(R.id.category))
                                .setText(R.string.no_category);
        } else {
            ((TextView) itemView.findViewById(R.id.category))
                                .setText(product.getCategory().getName());
        }
        return itemView;
    }
}

 

Como vemos la implementación es bastante simple y no tenemos que preocuparnos del tamaño de la lista o de devolver los identificadores para cada elemento. Sólo debemos añadir el constructor e implementar el método getView.

Ahora vamos a ver cómo utilizamos este adapter y por tanto la característica de relleno automático. El procedimiento es simple, partiendo de una actividad o fragment que implementa ListActivity o ListFragment respectivamente:

 

// Creamos un objeto ApplicationDataContext        
appDataContext = new ApplicationDataContext(this);

// Creamos el adapter que utilizará el DAO
adapter = new MyAdapter(this, R.layout.product);
setListAdapter(adapter);

// Le decimos al data context que rellene el adapter
appDataContext.productDao.fill("name");
appDataContext.productDao.setAdapter(adapter);

 

2. Databinding

Lo siguiente que vamos a ver es el databinding, que podemos definir de la siguiente forma:

  • Enlaza la vista con la entidad de forma automática
  • Rellenamos la vista con los datos del objeto
  • Rellenamos el objeto con los datos de la vista

Esto resulta muy útil cuando tenemos, como en nuestro ejemplo, formularios para la creación de objetos. Para hacer uso de esta característica lo primero que tenemos que hacer es anotar los atributos de nuestras clases de modelo que queremos enlazar con la vista, mediante la anotación @Databinding. A continuación haremos uso del método bind para rellenar la vista con los datos del bean y viceversa. Veamos cómo sería.

 
A. Diseñamos nuestro formulario

A continuación podemos ver el layout del formulario para la edición de un producto:
 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <LinearLayout
        android:id="@+id/botones"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="4dp"
            android:layout_weight="1"
            android:onClick="save"
            android:padding="4dp"
            android:text="@android:string/ok" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="4dp"
            android:layout_weight="1"
            android:onClick="cancel"
            android:padding="4dp"
            android:text="@android:string/cancel" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_above="@+id/botones"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/edit_title"
            style="@android:style/TextAppearance.DeviceDefault.Large"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="4dp"
            android:gravity="center"
            android:text="@string/new_product" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/name" />

        <EditText
            android:id="@+id/name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:inputType="text" />

        <Button
            android:id="@+id/category"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="4dp"
            android:onClick="changeProductCategory"
            android:text="@string/no_category" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/quantity_per_unit" />

        <EditText
            android:id="@+id/quantity"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:inputType="number" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/unit_price" />

        <EditText
            android:id="@+id/price"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:inputType="numberDecimal" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/stock" />

        <EditText
            android:id="@+id/stock"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:inputType="number" />
    </LinearLayout>

</RelativeLayout>

 

B. Anotamos nuestras clases de modelo

Lo siguiente es añadir la anotación @Databinding a los atributos de la clase Product que queremos enlazar a nuestra vista. A esta anotación añadiremos la propiedad ViewId que indica el identificador del campo de texto que enlazaremos con el atributo.

 

@TableField(name = "name", datatype = DATATYPE_TEXT, required = true)
@Databinding(ViewId = R.id.name)
private String name;
@TableField(name = "category", datatype = DATATYPE_ENTITY_REFERENCE, required = false)
private Category category;
@TableField(name = "quantity_per_unit", datatype = DATATYPE_INTEGER, required = true)
@Databinding(ViewId = R.id.quantity)
private int quantityPerUnit;
@TableField(name = "unit_price", datatype = DATATYPE_DOUBLE, required = true)
@Databinding(ViewId = R.id.price)
private double unitPrice;
@TableField(name = "units_in_stock", datatype = DATATYPE_INTEGER, required = true)
@Databinding(ViewId = R.id.stock)
private int unitsInStock;

 

Como vemos hemos anotado los atributos de la clase que queremos enlazar. Concretamente hemos dejado fuera del databinding la categoría, ya que vamos a crear un campo personalizado para esta propiedad.

 
C. Mostramos en la vista los datos del objeto

En el método onCreate, mostraremos los datos del objeto Product en la vista. Para ello utilizamos el método bind pasándole como argumento la Activity. Otra opción sería pasarle la vista (View) si no queremos que se busque en toda la vista de la Activity.

 

ApplicationDataContext dataContext = getApplicationDataContext();
Product product = dataContext.productDao.getElementByID(idProduct);
product.bind(this);

 

D. Recuperamos los datos de la vista en el objeto

Cuando vayamos a salvar el objeto en la base de datos, recuperaremos los datos introducidos por el usuario también con el método bind, pero pasándole en el segundo argumento DataBinder.BINDING_UI_TO_ENTITY y así le indicamos que no queremos rellenar la vista con los datos del objeto sino al revés.
 

ApplicationDataContext dataContext = getApplicationDataContext();
product.bind(this, DataBinder.BINDING_UI_TO_ENTITY);
product.setStatus(Entity.STATUS_UPDATED);
dataContext.productDao.save(product);

 

Como podemos ver, el databinding puede ahorrarnos la tarea de rellenar los campos de texto con el objeto y viceversa. Ahora pasamos a la última característca.

 

3. Validaciones

La última característica de ADA Framework que vamos a ver son las validaciones. Una vez que el usuario ha rellenado los datos en el formulario y nos hemos traído dichos datos mediante el método bind al objeto lo ideal es validar dichos datos antes de guardarlos en la base de datos. Para ello, ADA Framework proporciona las validaciones.

Las validaciones se definen mediante anotaciones en la clase del modelo. Tenemos soporte para validaciones estándar así como para definir nuestros propios validadores. Además podremos definir los mensajes de error en el caso de que no sean valores válidos.

Veamos cómo quedarían los atributos de Product con las validaciones:

 

@TableField(name = "name", datatype = DATATYPE_TEXT, required = true)
@Databinding(ViewId = R.id.name)
@RequiredFieldValidation(messageResourceId=R.string.error_empty_name)
private String name;
@TableField(name = "category", datatype = DATATYPE_ENTITY_REFERENCE, required = false)
private Category category;
@TableField(name = "quantity_per_unit", datatype = DATATYPE_INTEGER, required = true)
@Databinding(ViewId = R.id.quantity)
@RangeValidation(minValue = 1, maxValue = 9999, messageResourceId = R.string.error_quantity_range)
private int quantityPerUnit;
@TableField(name = "unit_price", datatype = DATATYPE_DOUBLE, required = true)
@Databinding(ViewId = R.id.price)
@CustomValidation(validator = PriceValidator.class, messageResourceId = R.string.error_price)
private double unitPrice;
@TableField(name = "units_in_stock", datatype = DATATYPE_INTEGER, required = true)
@Databinding(ViewId = R.id.stock)
private int unitsInStock;

 

Como podemos observar, se ha definido tres validaciones:

  • En el campo nombre el RequiredFieldValidation que indica que este valor no debe estar vacío
  • En el campo cantidad el RangeValidation que valida en valor para que esté en el rango indicado
  • En el campo precio un CustomValidation pasándole como argumento una clase validadora que veremos a continuación

Para definir una clase validadora simplemente creamos una clase, extendemos de Validator e implementamos el método Validate:

 

package es.androcode.adafw.products.model;

import java.lang.reflect.Field;

import com.desandroid.framework.ada.Entity;
import com.desandroid.framework.ada.validators.Validator;

public class PriceValidator extends Validator {

    @Override
    public Boolean Validate(Entity pEntity, Field pField, Object pAnnotation, Object pValue) {
        Double value = (Double) pValue;
        return value > 0.1;
    }
}

 

Como vemos resulta extremadamente sencillo definir nuestro validadores.

Ahora simplemente tenemos que llamar al método Validate sobre la clase del modelo y ésta devolverá true o false en función de si sus valores han pasado las validaciones. En el caso de que hubiera habido algún error, tenemos a nuestra disposición el método getValidationResult que devuelve una lista de objetos ValidationResult en el que cada uno representa un error de validación, incluyendo valores como el mensaje definido.

Veamos cómo sería el método que salva el objeto esta vez haciendo uso de la validación:
 

public void save(View view) {
    try {
        product.bind(this, DataBinder.BINDING_UI_TO_ENTITY);
        if (product.Validate(this)) {
            ApplicationDataContext dataContext = getApplicationDataContext();
            dataContext.productDao.save(product);
            setResult(RESULT_OK);
            finish();
        } else {
            List<ValidationResult> lst = product.getValidationResult();
            StringBuilder sb = new StringBuilder(getString(R.string.errors));
            for (ValidationResult vr : lst) {
                sb.append("n");
                sb.append(vr.getMessage());
            }
            Toast.makeText(this, sb.toString(), Toast.LENGTH_SHORT).show();
        }
    } catch (AdaFrameworkException e) {
        Log.e("Androcode", "Error guardando el producto: " + e.getMessage());
    }
}

 

Como vemos el funcionamiento es bastante sencillo y puede ahorrarnos algunas líneas de código.

Hasta aquí el repaso a las principales características de ADA Framework. Si queréis echarle un vistazo al código fuente lo tenéis disponible en el proyecto “adaframework-extras” en el siguiente repositorio:

 

Acerca del autor

FedeProEx

Ingeniero Informático en la Universidad de Sevilla, programador Java y amante del Heavy Metal. Soy desarrollador android fuera del horario de trabajo con algunas aplicaciones en el market como Tiempo AEMET o aconTags

  • http://adaframework.com DesAndrOId

    Una muy buena entrada haciendo un resumen de las principales características de la librería. Una vez más, un gran trabajo.

    Gracias

  • yonaides

    muy buenas practicas ya tengo ganas de probarlo

  • Angels

    Hola, soy novata en el mundo Android, estoy empezando mi primera aplicación, estudiando un poco el tema de la BD, me planteo la posibilidad de usar ORMLITE ó Data Framework. Tenéis conocomiento de la primera opción? Cual es la mas adecuada?

    Muchas gracias y un saludo.

    • FedeProEx

      Hola!,
      En breve intentaré publicar una entrada sobre la librería ORMLite. Si te sirve de ayuda yo es la que uso para la mayoría de mis desarrollos :)
      Saludos

  • ruth

    Hola necesito una ayuda como puedo conectar mi aplicacion android con base datos sql server 2008, he probado por jdbc y no se realiza la conexion gracias

  • Chema

    Hola, estoy haciendo un proyecto con ADA Framework y quería hacer un databinding de un campo RadioGroup, con varios RadioButton. ¿Cómo se haría el databinding?
    Muchas gracias.

  • Pingback: ADA Framework Tournament | Androcode()