Base de datos

¿Recordáis la app que contenía una lista en la que íbamos metiendo datos? Cuando cerrábamos la aplicación los datos desaparecían por no ser datos persistentes. La persistencia se puede lograr de 3 formas en Android.

  • Trabajando con ficheros
  • Trabajando con bases de datos
  • Trabajando con Preferencias

Trabajar con ficheros viene bien cuando se quiere guardar, por ejemplo, una copia de seguridad que mas tarde queramos restaurar. Tiene los inconvenientes habituales de los ficheros: a la hora de buscar no son lo mas eficientes.

Trabajar con Preferencias está bien para permitir que el usuario “personalice” la aplicación. En el móvil, por ejemplo, decidimos cada cuánto queremos que sincronice los datos con nuestra cuenta Google con Preferencias, aunque no lo sepamos. Siempre que una app tenga una opción de “ajustes”, normalmente una pantalla negra en la que el contenido está separado por grupos, estamos ante Preferencias. Quizá se podrían estirar un poco y lograr almacenar bastantes datos en ellas, pero no es para lo que están diseñadas y, por tanto, no será el planteamiento mas efectivo.

Las bases de datos las conocemos (¡o deberíamos!) todos. Eficientes para el almacenamiento y la recuperación de datos. En Android se puede usar, sin necesidad de meterle mas librerías ni nada, SQLite.

Clases necesarias

SQLiteOpenHelper

Antes que nada, necesitaremos tener una clase que herede de SQLiteOpenHelper. En ella debemos sobrecargar los métodos onCreate y onUpgrade, amén de llamar a super() en el constructor de la misma.

En el método onCreate será dónde pongamos el código para la creación de la base de datos. Aquí podemos ver los tipos de datos, algo necesario a la hora de crear las tablas de nuestra base de datos.

En el método onUpdate será dónde pongamos el código que altere la base de datos ya creada. En otras palabras, cuando nuestra aplicación vaya avanzando y queramos meter nuevas funcionalidades que precisen cambios en el modelo de datos, todo eso irá aquí. Tablas nuevas, Alter Table, Drop Table… todo ese código va en el onUpdate.

Además, la clase heredará los métodos getReadableDatabase() y getWriteableDatabase() que sirven para obtener acceso a la base de datos, sea en modo sólo lectura o en modo de lecto-escritura.

Las tablas de la base de datos deben usar todas como clave primaria el identificador _id, ya que funciones Android que se basan en este estándard.

En general se podría hacer todo el trabajo de crear/actualizar el modelo de datos desde una única clase que herda de SQLiteOpenHelper

SQLiteDatabase

Esta es la clase básica para trabajar con bases de datos SQLite en Android. Proporciona los métodos insert(), update()delete(), para insertar, actualizar y borrar registros.

Además proporciona el método execSQL() que sirve para ejecutar una sentencia SQL directamente.

El objeto ContentValues permite definir pares de clave/valor. La “clave” representa el nombre de la columna de la tabla, y el “valor” el contenido para dicho registro de la tabla en esa columna. Se pueden usar ContentValues para inserts y updates.

Las consultas pueden crearse con los métodos rawQuery()query() o con la clase SQLiteQueryBuilder .

rawQuery() acepta como entrada una sentencia SQL.

SQLiteQueryBuilder es una clase de ayuda para facilitar la construcción de consultas SQL.

query() ofrece una interfaz estructurada para especificar la consulta SQL.

Aquí vamos a estudiar el método query().


String[] columnas ={columna1, columna2...};

String[] selectionArgs = null;
String groupBy=" argumentos de la cláusula group by" , having="argumentos de la condición del group by" , orderBy ="campo o campos por los que ordenar, ASC o DESC" ;

String whereClause = "columna2=" + VALOR + " AND columna3>" + OTROVALOR;

Cursor c = getDb().query(NOMBRE_TABLA, columns, whereClause, selectionArgs, groupBy, having, orderBy);

En el array columns introduciremos el nombre de cada una de las columnas o campos de la tabla que queramos recuperar.

En whereClause pondremos los argumentos de la cláusula where. Ej: “campo1 = x OR campo2 =y”. Los valores concretos se pueden concatenar. Ojo con no meter “where campo1…”, el “where” ya lo pone el propio método query().

En el array selectionArgs se usa cuando en vez de concatenar los valores en la cadena whereClause usamos “marcadores”. Ej, en whereClause tenemos “campo1 = ? and campo2 < ?”. En tal caso, los valores que sustituyan esos marcadores (en el mismo orden, ojo) irían en el selectionArgs como cadenas, así: {valor_comparado_campo1_como_cadena, valor_comparado_campo2_como_cadena}

En groupBy meteremos los campos por los que queramos agrupar. Ojo, no hace falta “group by campo1, campo2”, sino “campo1, campo2”. Las funciones de agregación (SUM, AVG, etc) se emplean en el array columns, por ej {“campo2”, “sum(campo3)”}

En having haremos como en el whereClause, sólo que se usa nada mas para las consultas con groupBy.

En orderBy pondremos los campos (separados por “,”) por los que queramos ordenar el resultado, incluyendo al final si es ASCendente o DESCendente. Ej: “campo1, campo3 ASC”. Igual que antes, no es necesario incluir “Order by campo1 ASC”.

Salvo columns, el resto de argumentos puede ir nulo si no se precisan. Si, por ejemplo, se quieren sacar valores de toda la tabla, whereClause, selectionArgs, etc irían a nulo. Si no hay agrupación

Cursor

Las consultas devuelven un objeto Cursor. Este objeto representa el resultado de la consulta y apunta cada vez a un registro de la misma, actuando como buffer y no necesitando, así, cargar todos los datos en memoria a la vez.

Con el método getCount() podemos obtener el número de registros.

Para moverse entre registros puedes usar los métodos moveToFirst()moveToNext(). El método isAfterLast() permite comprobar si se ha alcanzado el final del resultado de la consulta.

Cursor ofrece métodos tipados  get*() como. getLong(columnIndex), getString(columnIndex), etc, para acceder a los datos de la columna cuya posición indica columnIndex. La posición será la misma que ocupe el identificador de la columna en el array columns de la consulta.

Ejemplo de Helper para nuestra aplicación ListaTonta1:

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class HelperDB extends SQLiteOpenHelper{

private final Context context;
public static final String NOMBRE_BASEDATOS = "grafica";
public static final int VERSION_BASEDATOS = 1;
public static final String BASEDATOS_TABLA_DATOS = "datos";
public static final String CAMPO_ROWID = "_id";
public static final String CAMPO_DATOS_TEXTO = "texto";

private static final String DATABASE_CREATE_DATOS=
"create table datos("+ CAMPO_ROWID +" integer primary key autoincrement," +
CAMPO_DATOS_TEXTO  +" text not null );" ;

// constructor
public HelperDB(Context contexto, String nombre,
CursorFactory factory, int version) {
super(contexto, NOMBRE_BASEDATOS, null, VERSION_BASEDATOS );
this.context = contexto;
}
//creación de la bbdd
@Override
public void onCreate(SQLiteDatabase db) {
//ejecución de creación de tablas y los índices pertinentes a cada tabla
db.execSQL(DATABASE_CREATE_DATOS);
}
//actualización de la bbdd
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//ejecución de modificación de las tablas existentes creación de nuevas tablas
}
}

Es recomendable emplear constantes públicas para todo lo relacionado con la base de datos. Así sólo habrá que cambiar en una única clase los valores que sean necesario cambiar (como, por ejemplo, la versión).

Usar constantes con los nombres de las tablas y campos nos ahorrará errores a la hora de lanzar las consultas. Para usarlas bastará con poner, por ejemplo, HelperDB.CAMPO_DATOS_TEXTO.

Cuando vayamos a hacer una modificación sobre nuestra base de datos ya creada, tenemos que tener dos cosas en cuenta. La primera, que tenemos que aumentar el valor de la constante VERSION_BASEDATOS, porque cuando se instancia, lo primero que hará será ver qué versión hay creada por la app (si la hay) y de no haberla o de ser esa versión inferior a la que tiene en código en este momento, ejecutaría el método onUpdate(). La segunda, que el código que modifique la base de datos tiene que ir en el onUpdate, no hay que cambiar el código de onCreate.

Para ejecutar las consultas, necesitamos un objeto SQLiteDatabase. Este objeto se obtiene de los métodos getWritableDatabase y getReadableDatabase, según si se quiere que se pueda o no se pueda escribir en la misma. El código para obtener esta instancia sería:

private SQLiteDatabase db;
HelperDB ayudante = new HelperDB(contexto, HelperDB.NOMBRE_BASEDATOS,
            		null, HelperDB.VERSION_BASEDATOS);
db = ayudante.getWritableDatabase();
db.setLocale(Locale.getDefault());

A partir de ahí, bastará con usar el objeto db para lanzar las consultas. Un ejemplo de ello:

//db ya inicializado antes
ContentValues values = new ContentValues();
values.put(HelperDB.CAMPO_DATOS_TEXTO, cadena_de_texto_a_insertar);
long _id =  db.insert(HelperDb.BASEDATOS_TABLA_DATOS, null, values);

El método insert devuelve el _id del registro recién insertado.
Los valores a insertar deberán ir en el ContentValues. Las columnas que estén definidas como not null y no tengan valor por defecto deberán añadirse al ContentValues obligatoriamente o dará error.
El argumento que pasamos a nulo siempre se pasa a nulo. Se llama nullColumnHack y sirve para permitir una inserción de valores nulos. Supón que tu tabla tiene todas las columnas que, o bien permiten nulos o bien tienen valores por defecto (como el autoincrement, el sysdate, o defaults que tú mismo le definieras). En ese caso podrías querer hacer
INSERT INTO mi_tabla;
pero SQLite necesita que le digas una columna a la que introducir algo, aunque ese algo sea un nulo.
INSERT INTO mi_tabla (una_columna) VALUES (NULL); Cuándo quieras pasarle un ContentValues vacío porque tu tabla permite campos nulos y/o toma valores por defecto, el argumento columnNullHack deberá ser el nombre de una de las columnas de tu tabla que acepte valores nulos, cualquiera de ellas.

//db ya inicializado antes
String[] columnas = {HelperDB.CAMPO_ROWID, HelperDB.CAMPO_DATOS_TEXTO};
String whereClause = null;
String[] selectionArgs = null;
String groupBy=null , having=null , orderBy = HelperDB.CAMPO_ROWID + " DESC" ;
Cursor c = db.query(HelperDb.BASEDATOS_TABLA_DATOS, columnas, whereClause, selectionArgs, groupBy, having, orderBy);
if ( c.moveToFirst()){
     do {
          Long _id = c.getLong(0);
          String texto = c.getDouble(1);
          // hacer algo con los datos
     } while(c.moveToNext());
}
c.close();

Aquí estamos lanzando una consulta para obtener TODOS (whereClause nulo) los campos _id y texto de la tabla DATOS, ordenados descendentemente por el campo _id. En otras palabras, queremos todos los datos introducidos, de mas reciente a mas antiguo.
IMPORTANTE: SIEMPRE EMPLEAR EL MÉTODO close() DEL CURSOR. NO DEJAR CURSORES ABIERTOS.
Para borrar tenemos el método delete.

//db ya inicializado antes
String whereClause = null;
String[] selectionArgs = null;
db.delete(HelperDb.BASEDATOS_TABLA_DATOS, whereClause, selectionArgs);

Este ejemplo borraría todo lo que haya en la tabla DATOS. Si queremos darle algún tipo de condición de borrado, whereClause y selectionArgs funcionan igual que en el método query().

Con lo aquí explicado ya debería bastar para hacer que nuestra aplicación ListaTonta1 guarde en una tabla lo que vayamos introduciendo. Si se preparan un objeto de transferencia de datos (DTO) o data access object (DAO), meramente un objeto que sirva para encapsular los datos que metemos en cada tabla, podrían intentar un método por el cual se pueda borrar el último elemento introducido empleando su _id en la cláusula where. Para guardar los datos sólo tienen que añadir la clase HelperDB a su proyecto y usarla tal cual está aquí indicado. Para el borrado sólo tienen que añadir un botón extra que sea el que use el método delete() sobre el registro con _id mas reciente.
Ya tienen deberes para practicar por su cuenta. ¡Pregunten si tienen dudas!

import android.content.Context;
Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: