domingo, 1 de abril de 2012

Persistencia de datos con SQLite sobre Android: parte 1.


Desarrollo sobre la plataforma Android:


Hola a todos nuevamente, después de un tiempito vuelvo a escribir, en este caso vamos a tratar sobre persistencia de datos, y para ello vamos a utilizar este pequeño pero extremadamente poderoso motor de Base de Datos, llamado SQLite. (como dato, este motor se utiliza en diferentes plataforma además de Android, por ejemplo IOS, BlackBerry,Google Chrome, Meego, Symbian, WebOs, etc, etc.). Cambien existen módulos para una gran cantidad de lenguajes de programación, y además este pequeño motor de bases de datos en su versión 3, tiene la capacidad de manejar una base de datos de hasta 2 Terabytes de tamaño. (creo que por ahora excede ampliamente nuestra , necesidades, digo por ahora, porque a este ritmo en unos años alcanzamos esas capacidades en nuestro móviles jejeje ).
Antes de empezar, un par de datos interesante del porque es tan popular, primero es libre, o sea, la podemos descargar de forma gratuita, y además si deseamos podemos bajarnos el código fuente, desde la pagina oficial:


Y otro dato, no menos importante, el binario a descargar por ejemplo para SO Linux, pesa menos de 300kb, el motor completo ;)

Ahora si vamos a comenzar a jugar un poco, primero debemos saber que la base de datos creada con SQLite, es un solo archivo, y este si realizamos todo de manera correcta se almacenara en la carpeta Datos/Datos/PackageName/Directorio de la Base de Datos. Para poder ver esto podemos utilizar una de las herramientas que nos provee el SDK Android, ya sea desde la shell con el comando adb, o desde nuestro Eclipse con la vista DDMS dentro de la pestaña File Explorer. Desde allí podemos copiar la DB, o realizar lo que deseemos con la misma.
Bueno ahora para comprender el funcionamiento, vamos a crear un proyecto desde el Eclipse, en este caso yo lo llamare PruebaSQLite, y utilizare la versión 2.2 de Android, como se ve en la imagen:



Bueno, lo primero que deberemos realizar es agregar una interfaz a nuestro proyecto, que herede de la interfaz BaseColumns. Y este sera nuestro código:

package com.prueba.gabriel.sqlite;

import android.provider.BaseColumns;
public interface Constantes extends BaseColumns{
// Nombre de la Tabla
public static final String TABLE_NAME = "PruebaSQLite";
// Columnas de la base de datos en PruebaSQLite
public static final String TIME = "tiempo";
public static final String TITLE = "titulo";

}

Cada evento se va a almacenar en una fila, cada una de ellas tendrá un _id que sera utilizado como primary key, y además contendrán los datos titulo y tiempo, que después usaremos para mostrar en pantalla. Ahora lo que deberemos hacer es agregar una clase a nuestro proyecto que extienda de SQLiteOpenHelper, que sera en si mismo la que cree y de forma a nuestra base de datos.



package com.prueba.gabriel.sqlite;

import static android.provider.BaseColumns._ID;
import static com.prueba.gabriel.sqlite.Constantes.TABLE_NAME;
import static com.prueba.gabriel.sqlite.Constantes.TIME;
import static com.prueba.gabriel.sqlite.Constantes.TITLE;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class PruebaSQLiteDataHelper extends SQLiteOpenHelper{
private static final String DATABASE_NAME = "PruebaSQLite.db";
private static final int DATABASE_VERSION = 1;


public PruebaSQLiteDataHelper(Context ctx) {
super(ctx, DATABASE_NAME, null, DATABASE_VERSION);
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_NAME + " (" + _ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + TIME
+ " INTEGER," + TITLE + " TEXT NOT NULL);");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}

}

Bueno como verán en esta nueva clase, nosotros al heredar de SQLiteOpenHelper, deberemos implementar dos métodos y el constructor de la clase. También se ha creado dos constantes, una con el nombre de la DB, y el otro para la versión. ( el numero de versión ahora no parece realmente importante, pero si tuviéramos nuestro programa en la market, y por algún motivo queremos que al actualizar cambie el programa se agreguen alguna columnas a la tabla o alguna que otra modificación importante, pues deberemos utilizar el numero de versión para que obligue a cambiarla con la actualización ;) usando el método onUpgrade).
Ahora modificaremos nuestro main.xml para que luego muestre los datos de nuestra db, quedando de esta manera:

<?xml version="1.0" encoding="utf-8"?>
<!-- En este xml simplemente vamos a mostrar un par de datos
! sacados de nuestra bd, recuerden que esto es una simple
!muestra de nuestro pequeño proyecto PruebaSQLite
-->

<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/textView"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</ScrollView>

Recuerden que el ID con el que identificaremos nuestro text view es textView, y que eh elegido un scrollview, porque si la salida de datos fuera demasiado extensa, de esta manera podríamos observarla por completo ;)
Ya casi terminamos, no se desesperen :D, ahora vamos a modificar nuestra clase principal donde haremos uso de la DB:



package com.prueba.gabriel.sqlite;

import static android.provider.BaseColumns._ID;
import static com.prueba.gabriel.sqlite.Constantes.TIME;
import static com.prueba.gabriel.sqlite.Constantes.TITLE;
import static com.prueba.gabriel.sqlite.Constantes.TABLE_NAME;
import com.prueba.gabriel.sqlite.PruebaSQLiteDataHelper;
import com.prueba.gabriel.sqlite.R;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.widget.TextView;

public class PruebaSQLiteActivity extends Activity {
private static String[] FROM = { _ID, TIME, TITLE, };
private static String ORDER_BY = TIME + " DESC";
private PruebaSQLiteDataHelper pruebaSQLite;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
pruebaSQLite = new PruebaSQLiteDataHelper(this);
try {
addEvent("Pruebas con SQLite");
Cursor cursor = getEvents();
showEvents(cursor);
} finally {
pruebaSQLite.close();
}
}
private void addEvent(String string) {
// Insertar un nuevo registro.
SQLiteDatabase db = pruebaSQLite.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(TIME, System.currentTimeMillis());
values.put(TITLE, string);
db.insertOrThrow(TABLE_NAME, null, values);
}

private Cursor getEvents() {
// Manejo de la query que vamos a necesitar
SQLiteDatabase db = pruebaSQLite.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, FROM, null, null, null,
null, ORDER_BY);
startManagingCursor(cursor);
return cursor;
}

private void showEvents(Cursor cursor) {
// mete todo en una gran string
StringBuilder builder = new StringBuilder(
"Saved events:\n");
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
long time = cursor.getLong(1);
String title = cursor.getString(2);
builder.append(id).append(": ");
builder.append(time).append(": ");
builder.append(title).append("\n");
}
// Muestra en pantalla
TextView text = (TextView) findViewById(R.id.textView);
text.setText(builder);
}

}

Como verán en el onCreate, después configurar a main como nuestra vista, creamos una instancia de PruebaSQLiteDataHelper, y como corresponde realizamos las operaciones dentro de un bloque try, y en el finally cerramos nuestra DB, para que de ninguna forma quede abierta ;) Cambien pueden observar que dentro del try solo hay tres cosas, la llamada a los métodos addEvent, a showEvent y un objeto Cursor, que los pasaremos a explicar ahora.

addEvent() lo vamos a usar para agregar nuevos registro a la db, para modificar los datos debemos llamar al método getWritableDatabase( ) , como el manejo de la bd esta alojado en la cache, uno puede llamar a este método las veces que uno crea necesario. Después creamos un objeto ContetValues al cual le pasaremos los datos que queremos agregar, y luego con insertOrThrow lo agregaremos a la bd. (este es un ejemplo simple, pero puede que esta operación desees realizarla dentro de un bloque try cath ;)).
Con el Objeto Cursor, nos ayudara a generar nuestras query sin problemas, tiene posibilidades mucho mas amplias que las que acá utilizamos, es solo cuestión de investigarlas un poco. Este objeto tiene un uso similar a un Iterator de Java o a un ResultSet de JDBC, donde podemos llamar a diferentes métodos del mismo para saber en que fila estamos, o pasara a la siguiente, etc etc. También como pueden ver, allí se realiza una llamada al método startManagingCursor, que realiza algo muy importante y es que nuestro cursor este manejado de acuerdo al ciclo de vida de nuestra actividad, por ejemplo desactivar el cursor cuando se detiene nuestra actividad, o que vuelva a consultar cuando la misma se reinicia.
Y por ultimo nos queda hablar del método showEvents, lo que hace este método es tomar como parámetro un objeto del tipo cursor y luego darle formato a la salida, para que el usuario pueda verla. Como se puede observar este no posee nada muy interesante solo comentare que se llama al método moveToNext(), dentro del método while, lo que hará que se repita el ciclo hasta que llegue al final de los datos, donde nos retornara un false y provocara que el ciclo while termine.
Después de toda esta explicación, deberíamos poder hacer correr nuestro programa, y debería tener una salida similar a esta:



Esto es todo por hoy, es lo mas simple dentro de las cosas que se pueden hacer con SQLite, mas adelante podemos hablar de cuestiones mas avanzada, y también del uso de los content provider para poder manejar información desde otras aplicaciones o tomar datos desde otros lugares ( cosa muy interesante :D )
Me tomo un poco mas de lo que pensaba escribir esto, pero se pudo, espero verlos mas seguido, y como siempre les dejo acá los enlaces de la docu oficial, y un par de enlaces de los cuales esta vez también saque info.

Documentación oficial:


Otros Enlaces:




Saludos a todos, Gabriel E. Pozo