Contenido sobre Android
Buscar
Social
Ofertas laborales ES
« Primer hangout de ADA Framework | Main | 50.000$ en premios para desarollo de videojuegos en Ouya »
miércoles
ene092013

Tutorial ADA Framework (parte I)

ADA Framework (Android Data Abstraction Framework) es un ORM supervitaminado. Está creado por Txus Ballesteros de Mob&Me. Si queréis escuchar un resumen muy completo de sus características podéis escuchar este podcast en Javahispano.

Como cualquier ORM nos va a permitir despreocuparnos de todo lo relacionado con la base de datos. Ya no será necesario crearnos nuestras sqls de creación de tablas o todas las clases dao. Simplemente, deberemos crearnos nuestras clases entidades, donde mediante anotaciones, estableceremos sus propiedades y sus relaciones. Pero además de ser un ORM también nos va a permitir gestionar las validaciones, maneja la relación entre nuestros views y los atritutos de las entidades (databinder), permite llenar ListViews, etc.

También cabe destacar su potencia. Podéis encontrar en el repositorio de github una prueba de rendimiento comparándolo con ORMLite en el que sale como un claro ganador. También es importante su pequeño tamaño, no superando los 73 kb. Un tamaño muy ajustado para todo lo que ofrece.

La aplicación de prueba.

En el tutorial simularemos la creación de una aplicación para gestionar la información básica de una academia. Vamos a gestionar los datos relacionados con los profesores, con los cursos que imparten y de los alumnos y los cursos a los que están inscritos.


Un profesor puede tener varios cursos pero un curso sólo puede estar impartido por un profesor, con lo que tenemos una relación 1:N. Así mismo, un alumno puede estar inscrito en varios cursos y cada curso puede tener varios alumnos (N:M).

 

Resumen de clases.

Las clases más importante de ADA framework son:

  • Entity: nuestros objetos planos tienen que extender de esta clase.
  • ObjectContext: es la encargada de crear-actualizar las tablas y nos dará acceso a nuestros objetos ObjectSet.
  • ObjectSet: es una colección con todas nuestras entidades y los métodos para insertar, actualizar, eliminar y buscar datos.

ENTITY.

Para definir una clase que queramos que persista es necesario que esta extienda de la clase Entity. En esta clase, mediante anotaciones, definiremos el nombre de la tabla @Table(name = "profesores") y el nombre y las características de cada campo @TableField(name = "numero_horas_clase", datatype = DATATYPE_INTEGER). Todas las anotaciones las encontramos en las clases annotations.Table y annotations.TableField.

A nivel de campo, le indicaremos el tipo (DATATYPE). Los tipos que nos permite usar SQLite son:

DATATYPE_BOOLEAN. *
DATATYPE_INTEGER.
DATATYPE_LONG.
DATATYPE_DOUBLE.
DATATYPE_REAL.
DATATYPE_TEXT.
DATATYPE_STRING.
DATATYPE_DATE.
DATATYPE_BLOB.
DATATYPE_ENTITY.
DATATYPE_ENTITY_REFERENCE: está deprecado en las últimas versiones ya que es sustituido por DATATYPE_ENTITY_LINK.

 

* En SQLite no existe el tipo de datos boolean así que se le trata como un integer con el valor 1-0.

En Android, para SQLite se recomienda crear una  clave única que sea el identificador. No es necesario declararnos ningún campo ID para estos menesteres pues ADA lo crea automáticamente y lo añade a nuestros objetos Entitys.

Mediante los tipos DATATYPE_ENTITY, DATATYPE_ENTITY_REFERENCE y DATATYPE_ENTITY_LINK podremos establecer relaciones entre nuestras clases. ENTITY_REFERENCE se utiliza cuando quieres hacer referencia a una tabla maestra que dispone de su propio ObjectSet y podemos usarle tanto con valores simples como con listas. El tipo ENTITY nos permite insertar una clave foránea en la tabla y hoy en día no admite listas. Con ENTITY_LINK podemos establecer relaciones 1:N entre entidades y entidades maestras.

Nuestra clase de profesores, además de tener sus datos básicos, debe tener los cursos que imparte. Aquí tenemos su definición:

 

@Table(name = "profesores")
public class Profesor extends Entity{
	
	@TableField(name = "numero_horas_clase", datatype = DATATYPE_INTEGER)
	private int numero_horas_clase;	
	@TableField(name = "nombre", datatype = DATATYPE_STRING, required = true)
	private String nombre;
	@TableField(name = "apellido", datatype = DATATYPE_STRING, required = true)
	private String apellido;
	@TableField(name = "dni", datatype = DATATYPE_STRING)
	private String dni;
	@TableField(name = "curso", datatype = DATATYPE_ENTITY)
	private List cursos;
				
	public Profesor(){
		cursos = new ArrayList();
	}
	
	public int getNumero_horas_clase() {
		return numero_horas_clase;
	}

	public void setNumero_horas_clase(int numero_horas_clase) {
		this.numero_horas_clase = numero_horas_clase;
	}
	
	public String getNombre() {
		return nombre;
	}
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	public String getApellido() {
		return apellido;
	}
	public void setApellido(String apellido) {
		this.apellido = apellido;
	}
	public String getDni() {
		return dni;
	}
	public void setDni(String dni) {
		this.dni = dni;
	}

	public List getCursos() {
		return cursos;
	}

	public void setCursos(List cursos) {
		this.cursos = cursos;
	}
		
}

 

Mediante el datatype DATATYPE_ENTITY le estamos indicando que debe guardar la relación de los cursos con los profesores. A nivel de base de datos, lo que está haciendo es añadir la clave del profesor en el curso. De esta forma, cuando pasemos la información de un profesor añadiremos los cursos que imparte. Si obtenemos la información de un profesor también obtendremos toda la información de los cursos.

Otras anotaciones importantes son:

  • isPrimaryKey: indicamos si el atributo tiene que formar parte de la clave primaria.
  • maxLength: longitud máxima.
  • required: si el campo es obligatorio
  • unique: si no puede haber dos registros con el mismo valor
  • virtual: se utiliza para campos que no deseamos que tengan persistencia. Son fundamentales, por ejemplo, para realizar sumatorios, obtener la mayor fecha, etc.

Si deseamos calcular el número total de horas que trabajan todos los profesores, tendremos que declararnos un campo virtual, y después, en el objectSet, mediante en método search, en el parámetro de los campos que deseamos recuperar tenemos que pasar el campo sum(xxx) as xxx. Más adelante profundizaremos en la clase objectSet, por ahora sólo necesitamos saber, que es la responsable entre otras cosas de permitirnos realizar búsquedas.

@TableField(name = "sum_numero_horas_clase", datatype = DATATYPE_INTEGER, virtual = true)
private int sum_numero_horas_clase;

//search(pDistinct, pFields, pWherePattern, pWhereValues, 
          pOrderBy, pGroupBy, pHaving, pOffset, pLimit);

List profesores = contextoDatos.profesorDao.search(false, 
	new String[]{"sum(numero_horas_clase) as sum_numero_horas_clase"}, 
	null, 
	null, 
	null, 
	null, 
	null, 
	null, 
	null);

*Si nuestra clase está relacionada con otras, como es el caso de profesores, no funcionaría. Es decir, sólo podemos aplicar funciones de agregado a clases que no contengan otras.

OBJECTCONTEXT.

Una vez que disponemos de nuestras entidades debemos instanciar los objetos ObjectSet en la clase ObjectContext.Esta clase es la encargada de realizar todo el trabajo acceso a la base de datos, ya sea para crear o actualizar el esquema de la base de datos o para obtener acceso a los registros de la misma.

En nuestra aplicación debemos crearnos una clase que extienda de ObjectContext.

public class ContextoAplicacionDatos extends ObjectContext {
			
  public ObjectSet profesorDao;
  public ObjectSet alumnoDao;
  public ObjectSet cursoDao;
  public ObjectSet alumnoEnUnCursoDao;	
	
  public ContextoAplicacionDatos(Context pContext) throws AdaFrameworkException {
    super(pContext, "AdaFramework_test.db");				
		
    if (profesorDao==null)
      profesorDao = new ObjectSet(Profesor.class, this);
    if (alumnoDao==null)
      alumnoDao = new ObjectSet(Alumno.class, this);
    if (cursoDao==null)
      cursoDao = new ObjectSet(Curso.class, this);
    if (alumnoEnUnCursoDao==null)
      alumnoEnUnCursoDao = new ObjectSet(AlumnoEnUnCurso.class, this);		
  }	
}

* Evita crear campos privados en esta clase que no sean los ObjectSet. Se produce un problema en el acceso a estos campos que impide poder abrir la base de datos.

En la clase ContextoAplicacionDatos realizamos dos tareas:

  1. Declaramos e instanciamos todos nuestros objetos ObjectSet.
  2. Llamamos al constructor de la clase ObjectContext pasándole al menos el contexto.

La clase ObjectContext tiene tres constructores:

  • ObjectContext (Context pContext)
  • ObjectContext (Context pContext, String pDatabaseName)
  • ObjectContext (Context pContext, String pDatabaseName, int pDatabaseVersion)

Si no le pasamos el nombre de la base de datos y la versión el genera un nombre por defecto y toma el control de las versiones. Recordemos que Android utiliza el nombre de las versiones para saber si tiene que llamar al método onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) de la clase SQLiteOpenHelper para actualiar el esquema de la base de datos.

Alguno de los métodos que nos encontramos en esta clase son:

  • deleteDatabase: elimina la base de datos físicamente.
  • backup: para realizar una copia de la base de datos a la carpeta de la aplicación o al fichero indicado mediante backup(File pDestinationFolder).
  • onPopulate (SQLiteDatabase pDatabase): método para llenar las tablas con los datos iniciales de la apliación. Por ejemplo, si tenemos una tabla con los países es aquí donde realizaremos los insert.
  • onPreCreate (SQLiteDatabase pDataBase): método al que se llama antes de llamar al método onCreate para la creación del esquema de la base de datos.
  • onCreate (SQLiteDatabase pDataBase) throws AdaFrameworkException: se crea la base de datos.
  • onPostCreate (SQLiteDatabase pDataBase): método que se llama después de haber creado la base de datos.
  • onPreUpdate (SQLiteDatabase pDataBase, int pOldVersion, int pNewVersion): lo mismo que para la creación de la base de datos cuando lo que vamos a hacer es una actualización.
  • onUpdate (SQLiteDatabase pDataBase, int pOldVersion, int pNewVersion) throws AdaFrameworkException
  • onPostUpdate (SQLiteDatabase pDataBase, int pOldVersion, int pNewVersion)
  • getReadableDatabase (): obtenemos acceso a la base de datos sólo en modo de lectura.
  • getWritableDatabase (): obtenemos el acceso a la base de datos en modo de escritura. 
  • executeQuery (SQLiteDatabase pDatabase, Boolean pDistinct, String pTable, String[] pColumns, String pSelection, String[] pSelectionArgs, String pGroupBy, String pHaving, String pOrderBy, String pLimit): permite ejecutar una sentencia SQL contra una tabla podemos indicar los campos select, los where, el order by, group by, etc.

Los siguientes tres métodos ejecutarían sentencias de insercción, actualización y eliminación de datos. Naturalmente, no será necesario utilizar ninguno de estos métodos cuando estemos trabajando con los ObjectSets pues ellos ya realizan todas estas tareas aislándonos de las características de una base de datos relacional.

  • executeInsert (SQLiteDatabase pDatabase, String pTable, String pNullColumnHack, ContentValues pValues):
  • executeUpdate (SQLiteDatabase pDatabase, String pTable, ContentValues pValues, String pWhereClause, String[] pWhereArgs)
  • executeDelete (SQLiteDatabase pDatabase, String pTable, String pWhereClause, String[] pWhereArgs)

ObjectSet.

Esta clase es una lista que contendrá todas las entidades que insertamos o seleccionemos de la  base de datos. Nos proporciona todos los métodos que necesitamos para realizar las tareas CRUD (Crear, Obtener, Actualizar y Borrar).

Si queremos añadir una entidad podremos usar el método add(). Este método no inserta la entidad en la base de datos, simplemente la añade a la lista. También podemos añadir una entidad en una posición determinada de nuestra ObjectSet con add(final int location,final T object) o añadir directamente una lista con addAll (Collection<?extends T > collection).

Para insertar los datos, tendremos que utilizar el método save(). También podemos salvar los datos sin necesidad de haberles añadido con el método save(T pEntity). En este caso, el registro no se conservará en el ObjectSet con lo que si ejecutamos el método get(int indice) nos devolverá una excepción. Para usos normales, es más óptimo añadir la entidad y después salvarla, pues de esta forma, no es necesario volver a recuperar de la base de datos la entidad.

El método save, no sólo nos permite realizar inserts, sino que es el encargado de actualizar o eliminar un registro. Para que ADA sepa qué acción debe realizar en cada momento, tenemos que indicarle en el método setStatus de la entidad si deseamos insertar el registro, actualizarlo o eliminarlo.

//Insertamos la entidad curso.
Curso curso = new Curso();
curso.setNombre("GESTIÓN DE RECURSOS");
curso.setNumeroMaximoAlumnos(50);
curso.setPrecioHora(2.25);			
curso.setStatus(Entity.STATUS_NEW);			
												
contextoDatos.cursoDao.add(curso);
contextoDatos.cursoDao.save();
	
//Eliminamos el curso que hemos añadido. 
curso1 = contextoDatos.cursoDao.get(0);
curso1.setStatus(Entity.STATUS_DELETED);
contextoDatos.cursoDao.save();
	
//Actualizamos una entidad.
curso = contextoDatos.cursoDao.get(0);
curso.setNumeroMaximoAlumnos(35);
curso.setStatus(Entity.STATUS_UPDATED);
contextoDatos.cursoDao.save();



Para las búsquedas disponemos de los métodos fill():

  • void fill (): carga todos los registros en el objectSet.
  • void fill (Integer pLimit): carga hasta un número máximo de registros.
  • void fill (Integer pOffset, Integer pLimit): le indicamos desde qué registro hasta cuál tiene que realizar la carga.
  • void fill (String pOrderBy): recupera todos los registros ordenándolos según el parámetro.
  • void fill (String pOrderBy, Integer pLimit): ordena con un límite.
  • void fill (String pOrderBy, Integer pOffset, Integer pLimit): ordena los registros comprendidos entre el pOffset y número máximo.
  • void fill (String pWherePattern, String[] pWhereValues, String pOrderBy): podemos ejecutar un where y ordenar los registros.

También disponemos de los métodos search para realizar las búsquedas. Estos nos permiten realizar búsquedas más completas, estableciendo si la sql tiene que trabajar con el DISTINCT, los campos que queremos recuperar, el where, etc, y ejecutar las funciones de agregados.

  • List< T > search (Boolean pDistinct, String pWherePattern, String[] pWhereValues, String pOrderBy, String pGroupBy, String pHaving, Integer pOffset, Integer pLimit) throws AdaFrameworkException
  • List< T > search (Boolean pDistinct, String[] pFields, String pWherePattern, String[] pWhereValues, String pOrderBy, String pGroupBy, String pHaving, Integer pOffset, Integer pLimit) throws AdaFrameworkException

 La diferencia entre los métodos fill y search, es que mediante una búsqueda fill se devolverán todas las entidades relacionadas con el objeto propietario. Por ejemplo, si tenemos un pedido, que está formado por una lista de líneas de pedido, al hacer la búsqueda mediante el método fill, recibiremos tanto el objeto pedido como sus líneas. Si hacemos la búsqueda mediante el método search, sólo recibimos la entidad maestra. En el caso de los pedidos, tendríamos acceso al objeto pedido, a nivel de cabecera, pero no a sus líneas.

References (2)

References allow you to track sources for this article, as well as articles that were written in response to this article.

Reader Comments (6)

Excelente tutorial y en este momento me viene de maravillas ya que estoy haciendo un app para afinar mis conocimientos en la plataforma del robot verde, escuche el podcast y vi los vídeos del sitio y debo decir que el potencial de este framework es brutal, para mi es claro Ada framework para Android y Core Data para iOS.

Sigan así y saludos desde Chile

enero 11, 2013 | Unregistered CommenterFrancisco Javier

Muchas gracias. Sin duda después de ver unos cuantos frameworks para el tratamiento de datos con Android este es el que más me llama. Además, su evolución es contínua, se integra perfectamente en un proyecto Android y te provee todas las cosas que inicialmente uno pueda necesitar y alguna más.

enero 11, 2013 | Registered Commenterjtristan

Hola a todos, yo no puedo entrar a valorar si la librería es buena o mala ya que seguramente no sería objetivo, lo que si quiero es ofreceros nuestra ayuda sobre cualquier duda que os pueda surgir a la hora de utilizarla.

Me gustaría animaros a formar parte de la recien estrenada comunidad de desarrolladores en Google Plus. ADA Framework Developers

Os deseo un muy buen desarrollo.

enero 12, 2013 | Unregistered CommenterMob&Me

Hola a todos,
he estado trabajando con este Framework, me parece interesante, pero he tenido unos errores que no he podido solucionar, les agradeceria si me pudieran pasar el codigo fuente de este ejemplo.

febrero 12, 2014 | Unregistered CommenterJoaquín

No habia visto un tutorial tan completo para un ORM para android te felcictio y muchas gracias.

noviembre 5, 2014 | Unregistered Commentersergio

Hola me interesa su post pero quisiera saber si tiene en elgun lugar el codigo fuente de este ejemlo mas que nada pq quiero entender como hacer la relacion N:M que esta representada en su diagrama pero luego no esta explicada en codigo. Muchas gracias

enero 11, 2016 | Unregistered CommentermujeresProgramadoras

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>