Bases de Datos y conexion con un ListView utilizando vistas personalizadas

“Desarrollo de Aplicaciones Móviles en Android”
Ejercicio Avanzado A: Bases de Datos y conexión con un
ListView utilizando vistas personalizadas

A. Descripción
Con este ejercicio, veremos cómo funcionan las bases de datos SQLite en Android, crearemos una, y la conectaremos con una actividad en forma de ListView muy simple. A continuación, el alumno deberá mejorar la forma en que se visualizan de forma que sea elegante sin que la interfaz de usuario sufra ralentización alguna.
Las ListViews son muy importantes en toda aplicación Android. Es más, hay aplicaciones cuyas interfaces se construyen alrededor de ListViews personalizadas, como los clientes de Twitter o de Facebook. Para almacenar información en el dispositivo Android, podremos recurrir a una base de datos de tipo SQLite, que nos permitirá un acceso rápido a los datos en comparación con el sistema de archivos, pero tenemos que tener siempre en cuenta
que los dispositivos Android están siendo distribuidos con poca memoria interna (o ROM), que
es donde se almacena la base de datos, por lo que no conviene abusar de ella.
B. Implementación
B---1. Construcción de la base de datos
Para construir una base de datos, necesitaremos al menos dos clases. En la primera, representaremos lo que vamos a guardar en cada fila de la tabla como un objeto Java, siguiendo un poco los patrones JavaBean y DAO (o Data Access Object). En la segunda clase,
tendremos los métodos encargados de construir la tabla, de actualizarla, de borrarla, y de insertar los valores iniciales. ¿Cuál es el objetivo? Nosotros construiremos una capa encima de
la base de datos, de forma que no veremos un “SELECT * FROM …” en nuestro código a menos
que sea en estas dos clases.
En SQLite, disponemos de unos recursos limitados, ya que la idea de SQLite es de ocupar el menor espacio posible. Tanto es así, que no existen claves ajenas o foráneas, ni existen los tipos de fecha (Date), booleano (Boolean), ni tantos otros a los que estamos acostumbrados en SQL de sobremesa y servidores. Esto nos llevaría, en caso necesario, a forzar manualmente la comprobación de claves ajenas, y en el caso de querer guardar tipos que no sean caracteres (TEXT) o números (INTEGER), tendremos que realizar conversiones,
como hemos hecho con la clase DbCryptoAlgorithm. Para empezar, veamos sus datos:

public enum TipoDeCifrado {
Bloque,
Flujo
}

public enum TipoDeSimetria {
Simetrico,
Asimetrico
}
private static final String LOG_TAG = "DbCryptoAlgorithm";
private long id = -1;
private String nombre = "";
private TipoDeCifrado tipoCifrado; private TipoDeSimetria tipoSimetria; private boolean seguro = false;

private int longitudMinimaClave = -1;

Primero nos creamos unos tipos enumerados, luego la etiqueta para el LOG de esta clase, y las variables que representarán las filas. El id, el nombre del algoritmo, tipo de cifrado, tipo de simetría, si es seguro y la longitud mínima de clave recomendada. Para poder acceder a las columnas de las filas, crearemos variables a las que llamaremos “llaves” para poder acceder a ellas más tarde. Esto nos permitirá no tener problemas para encontrar las
columnas en el código. Ahora, necesitamos un método para poder guardar el contenido de
estas variables en la base de datos, esto lo haremos en el siguiente método:

public long save(SQLiteDatabase db) { ContentValues cv = new ContentValues();
// para cada variable
cv.put(__LLAVE_VARIABLE , valorVariable);

}

ContentValues es una clase compuesta por una serie de parejas (llave, valor). Lo que aquí hacemos es guardar todas las variables con su correspondiente llave (ver el método completo en el ejercicio Inicial). Por supuesto, también tenemos que controlar si la variable
está representada o no en la base de datos, y eso lo hacemos mirando el id. Si es distinto de
---1 (valor por defecto al crear un objeto de la clase), sabemos que ya ha sido creado, y por tanto
que debemos actualizarlo. Y si no, lo insertamos.
Debemos ver ahora la segunda clase, la DbCryptoAlgorithmSQLiteHelper. A
través de esta clase podremos leer y escribir en la base de datos, pero eso se verá más tarde. Lo importante es ver que en esta clase definimos el nombre de la base de datos, el nombre de la tabla, y la versión actual del esquema en el constructor. Lo de las versiones es importante,
ya que lo normal es que a medida que vayamos avanzando en el desarrollo de una aplicación ya puesta a disposición de nuestros usuarios, queramos incluir más funcionalidades, y por muy previsores que seamos, alguna columna se nos escapará. Lo recomendado en estos casos suele ser copiar los datos del usuario a una tabla auxiliar, borrar la tabla, crear la nueva, e inicializarla. El “ALTER TABLE” está soportado por el estándar SQLite, pero no se recomienda su uso para cambios en el esquema de tablas en Android. Suele ser preferible el método descrito
anteriormente.
En el constructor de nuestra clase, llamamos a un método de la clase padre donde especificamos el contexto que solicita crear una instancia de la base de datos, la tabla que se quiere, una factoría de cursores (pasamos null) y la versión del esquema (de la base de
datos) actual.
Veamos los métodos más importantes de la clase SQLiteOpenHelper:

public void onCreate(SQLiteDatabase  db) {…}
public void onUpgrade(SQLiteDatabase  db, int versionAntigua, int
versionNueva) {}

public void onOpen(SQLiteDatabase  db) {…}

El primer método es el que se llama para crear la base de datos, es decir, donde está la sentencia SQL “CREATE TABLE…”. Justo después tenemos una llamada a un método auxiliar, que es donde estamos introduciendo los datos nada más crear la tabla. Si lo vemos, nos daremos cuenta de que no hay llamadas a sentencias SQL de tipo “INSERT…”, porque lo hacemos todo a través de DbCryptoAlgorithm. Solamente instanciamos un objeto, le damos sus valores, y utilizamos el método save() pasándole la instancia de la base de datos.
Simple.
El método onUpgrade() es llamado cuando se detecta que la versión del esquema de la base de datos, es inferior a la que requiere nuestro programa (versionAntigua < versionNueva), por lo que aquí debemos actualizar la base de datos. Generalmente, se copian los datos a una tabla auxiliar, se borra la tabla, se llama al onCreate() para que genere la nueva tabla, se copian los datos de la tabla auxiliar y luego se borra ésta. Es de suponer que cuantas menos versiones del esquema de base de datos tengamos que soportar
en nuestra aplicación, mejor, luego cada vez que hagamos un cambio en la versión del esquema, que sea con vistas al futuro. Por último, el onOpen() (que no está ni es necesario
en este ejercicio) se llama cada vez que solicitemos abrir la base de datos, ya sea para leer o
escribir.
Teniendo nuestra clase SQLiteOpenHelper completamente definida, nos falta terminar la clase DbCryptoAlgorithm. ¿Qué nos falta? Un par de métodos.

public static Cursor getAll(SQLiteDatabase db) {}
public static DbCryptoAlgorithm loadFrom(SQLiteDatabase db, long id) {

public DbCryptoAlgorithm loadFrom(Cursor c) {

Vayamos por partes. El último método se encarga de, dado un cursor apuntando a la fila que queremos, transferir los datos de las columnas a las variables del objeto. Esto se hace pidiéndole los datos al cursor con las llaves, y es muy sencillo. Segundo, en el loadFrom()
estático, se nos da una instancia de la base de datos y un id, y lo que queremos es devolver un
objeto de la clase DbCryptoAlgorithm que recupere la información de la fila con ese id, cosa que haremos utilizando el método anterior. ¿Cómo? Pues necesitamos ejecutar una sentencia SQL buscando por el id; si vemos que en el cursor hay al menos una fila, devolvemos la primera (lo correcto es asegurarse de que sólo hay una fila, por supuesto). Por
último, tenemos el método getAll(), que es el que ya utilizamos en el ejercicio inicial; en este método, simplemente hacemos un “SELECT *” para recoger en el cursor todas las filas de la tabla.
¿Ya está? Sí. Hasta aquí preparar la base de datos. Tened en cuenta que los métodos
en sí no son complicados, sino que la complejidad se ve aumentada por el hecho de que SQLite no es tan versátil como las demás implementaciones de SQL a las que estamos acostumbrados, forzándonos a guardar objetos Date de Java como Strings (TEXT) y rescatarlos utilizando
un parser, por ejemplo.
B---2. Conectando con la lista
Para llegar hasta el ejercicio inicial, tenemos que hacer muy poco. Primero, definimos
variables para la lista, la base de datos, y el cursor que contendrá las filas representadas en la lista. En nuestro método initConfig(), lo que hacemos es solicitar una base de datos a
través de nuestro DbCryptoAlgorithmSQLiteHelper, inicializar nuestro cursor, crear
un adaptador utilizando un esquema (list_item.xml) muy sencillo y asignarle a la lista este adaptador. Es importante tener en cuenta la línea startManagingCursor(c), ya que gracias a ella no tenemos que preocuparnos de cerrar el cursor ni de hacer que la ListView pase por todas las filas del mismo. Por último, debemos implementar el método onDestroy(), y cerrar la base de datos aquí. Es importante saber que no se debe cerrar la
base de datos tras los cambios, sino cuando se cierre la actividad (onDestroy()) que realiza
las llamadas, ya que si no se intentará leer/escribir en una base de datos cerrada y habrá
excepciones.
C. Pasos a seguir
1. Para este ejercicio, lo que haremos es mejorar la visualización de las filas de la base de datos. Lo primero que tenemos que hacer es realizar cambios en el list_item.xml, porque ahora tendremos un CheckBox y un par de
TextViews. Lo que queremos mostrar es el título de los algoritmos
criptográficos, la longitud de la clave, el tipo de cifrado y el tipo de simetría. Cómo quedan dispuestos se deja a la elección de los alumnos. Además, se pide que el CheckBox y el fondo de cada fila varíen según si el algoritmo es seguro o no; en el caso del CheckBox, que esté marcado si es seguro, y que el fondo sea distinto
para los algoritmos seguros y para los no seguros.
2. Después de hacer cambios en el list_item.xml, necesitaremos crear dos clases privadas en DbListViewActivity: DbCryptoAlgorithmAdapter (que extiende CursorAdapter) y DbCryptoAlgorithmWrapper. Empezaremos por la segunda. En DbCryptoAlgorithmWrapper, tendremos
un objeto privado por cada View en el list_item.xml, por ejemplo, el CheckBox y dos TextViews. Además, necesitaremos guardar la fila, y la guardaremos como un tipo View, aunque podemos cambiarlo a
LinearLayout, TableLayout, RelativeLayout, etc. según sea el caso. A
esta clase la conoceremos como clase envoltorio o Wrapper, porque se encarga
de llevar los datos de la fila apuntada por el cursor a las vistas de cada fila de la
ListView. El constructor será así:

DbCryptoAlgorithmWrapper (View view) {
this.row = view;

}

3. Ahora, necesitamos getters para cada vista de la fila (el CheckBox y los
TextViews), que implementaremos todos de forma similar:

public CheckBox getSeguro() {
if (seguro == null)
seguro = (CheckBox) row.findViewById(R.id.rowCheckBox); return seguro;

}
4. El por qué implementaremos todos los getters de esta forma debe estar claro para
el alumno. El último método de esta clase es

public void populateFrom(Cursor c) {
}
En este método es donde realizaremos los cambios según cómo sea el algoritmo apuntado por el cursor. Su contenido se deja completamente como ejercicio. Lo que debemos hacer es inicializar las vistas de nuestro Wrapper en función de la fila apuntada por
el cursor, y luego, en función de qué tipo de algoritmo se trate, modificar el fondo de la fila y
marcar el CheckBox.

5. Ya hemos terminado la clase Wrapper, ahora tenemos que hacer nuestro
adaptador. La idea de éste es que utilizaremos las llamadas que nos provee la API
de Android para re---utilizar las Views, es decir, en vez de crear una nueva vista cada vez que Android debe mostrar una fila (éste es el comportamiento por defecto haciendo scrolling), re---utilizaremos las vistas que ya se han creado, actualizando su contenido. Android nos lo pone muy fácil, y en realidad la mayor parte del trabajo ya lo hemos hecho en la clase Wrapper. Para que esto funcione,
para cada vista que representa una fila debemos asociarle una clase Wrapper, y
el hecho de re---utilizar las vistas nos pondría las cosas muy difíciles si tuviéramos
que llevar a cabo la organización nosotros (los getters dentro de la clase Wrapper
requerirían siempre buscar la vista de la fila si no lo hiciéramos, lo cual sería muy malo), pero Android nos lo pone muy fácil, porque nos deja asociar un objeto a las vistas (métodos setTag() y getTag()), los cuales usaremos.

Empecemos. Necesitamos declarar el constructor, que haremos así:

public DbCryptoAlgorithmAdapter(Context  context, Cursor c) {
super(context, c);

}
6. Ahora, necesitamos implementar el método bindView. Este método se llama
cuando Android nos entrega una vista ya inicializada (o inflada con su Wrapper) y
nos pide que le asociemos los datos correspondientes a la fila del cursor. Es
también muy fácil:
public void bindView(View view, Context context, Cursor cursor) { DbCryptoAlgorithmWrapper wrapper = (DbCryptoAlgorithmWrapper)
              view.getTag();
           wrapper.populateFrom(c);

       }

7. Como se puede observar, el método es muy sencillo, ya que todo el trabajo lo hace la clase Wrapper. Lo último del adaptador es el método newView, que se llama cuando hay que crear una vista que represente la fila apuntada por el cursor. Lo
que tenemos que hacer es inflar una vista siguiendo el esquema list_item.xml, crear una clase wrapper alrededor de esta vista, asignársela con setTag(), y dejar que el wrapper realice el resto del trabajo.

public View newView(Context context, Cursor cursor, ViewGroup parent) { LayoutInflater inflater = getLayoutInflater();
View row = inflater.inflate(R.layout.list_item, parent, false);
// creación del wrapper
// asignacn del wrapper a la vist
// cargar contenido del cursor en la vista a través del wrapper

}

Las líneas comentadas representan líneas de código a completar por los que usted crea convenientes
La clase LayoutInflater nos permite transformar un esquema XML en
una vista, y el último parámetro del método inflate() es importante porque une asigna la
View a la View que hace de padre (en este caso, la ListView), y si lo dejamos en true,
provocará una excepción.