En este tutorial descubriremos como usar bases de datos en SQLite
para no perder la información de nuestras Aplicaciones Android. A medida que vayamos avanzando veremos la utilidad de clases como SQLiteOpenHelper
, SQLiteDatabase
, Cursor
, CursorAdapter
y de Herramientas como sqlite3 y SQLite Browser.
Asumimos que tienes conocimientos básicos en SQLite
Con el fin de facilitar tu aprendizaje usaremos un ejemplo de guía, que te permitirá ir la práctica en paralelo a las teorías y conceptos estudiados. Puedes descargar los recursos desde aquí:
Descargar CódigoTenlo a mano para comprender los siguientes temas:
- ¿Que es SQLite?
- Diseño de la Aplicación
- Crear una Base de Datos
- Crear el Esquema de la Base de Datos
- CRUD
- Insertar Registros
- Consultar Registros
- Borrar Registros
- Actualizar Registros
- sqlite3
- SQLite Browser
- La Clase CursorAdapter
- La Clase SimpleCursorAdapter
¿Qué es SQLite?
Es un ligero motor de bases de datos de código abierto, que se caracteriza por mantener el almacenamiento de información persistente de forma sencilla. A diferencia de otros SGBD como MySQL, SQL Server y Oracle DB, SQLite tiene las siguientes ventajas:
- No requiere el soporte de un servidor: SQLite no ejecuta un proceso para administrar la información, si no que implementa un conjunto de librerías encargadas de la gestión.
- No necesita configuración: Libera al programador de todo tipo de configuraciones de puertos, tamaños, ubicaciones, etc.
- Usa un archivo para el esquema: Crea un archivo para el esquema completo de una base de datos, lo que permite ahorrarse preocupaciones de seguridad, ya que los datos de las aplicaciones Android no pueden ser accedidos por contextos externos.
- Es de Código Abierto: Esta disponible al dominio publico de los desarrolladores al igual que sus archivos de compilación e instrucciones de escalabilidad.
Es por eso que SQLite es una tecnología cómoda para los dispositivos móviles. Su simplicidad, rapidez y usabilididad permiten un desarrollo muy amigable.
SQLite tiene ciertas limitaciones en algunas operaciones. Por ejemplo, no se puede implementar las clausulas FULL OUTER JOIN
y RIGHT OUTER JOIN
. Aunque en la mayoría de casos esto no afectará, siempre habrán otras alternativas como JavaDB o MongoDB.
Para comenzar a probar nuestras bases de datos, quisiera explicarte un poco mas sobre el ejemplo práctico que vamos a desarrollar. Se trata de una aplicación llamada "Quotilius"
. Cuyo objetivo es guardar una lista de frases celebres.
Debemos almacenar el cuerpo de la frase y el autor que la trajo al mundo. El siguiente sería un bosquejo rápido de como se vería la tabla de frases:
_id body author
1 "El ignorante afirma, el sabio duda y reflexiona" Aristóteles
2 "Hay derrotas que tienen mas dignidad que la victoria" Jorge Luis Borges
3 "Si buscas resultados distintos, no hagas siempre lo mismo" Albert Einstein
4 "Donde mora la libertad, allí está mi patria" Benjamin Franklin
5 "Ojo por ojo y todo el mundo acabará ciego" Mahatma Gandhi
La idea es proporcionarle al usuario la posibilidad de añadir nuevas frases desde la Action Bar.
Diseño de la Aplicación
Quotilius se compone de dos actividades para su funcionamiento. La primera es Main
. Esta actividad está basada en una lista, donde ubicaremos las frases del usuario. No definiremos su archivo de diseño debido a que será una subclase de ListActivity
. Así que usaremos el layout del sistema android.R.layout.list_content
para inflar la actividad.
La segunda actividad se llama Form
, la cual cumple la función de formulario para ingresar el cuerpo de la frase y el autor mediante dos Edit texts. Se ejecutará cuando presionamos el Action Button "Añadir". Observemos su previsualización:
La comunicación entre ambas actividades se realiza a través de un Intent explicito. Este nos permite obtener los datos que el usuario ingresó en Form para poder usarlos en Main e ingresarlos en la base de datos y al mismo tiempo actualizar nuestro ListView
.
Lo primero que harás será enviar un Intent para ejecutar la actividad Form
a través de un canal de comunicación:
//Código de envío
public final static int ADD_REQUEST_CODE = 1;
...
//Iniciando la actividad Form
Intent intent = new Intent(this, Form.class);
//Inicio de la actividad esperando un resultado
startActivityForResult(intent, ADD_REQUEST_CODE);
Una vez el usuario este en la actividad y haya presionado el botón de "Guardar", devuelves los textos en los textviews por el mismo canal hacia Main
:
//Obtener los datos de los campos
EditText quoteField = (EditText) findViewById(R.id.quoteField);
EditText authorField = (EditText) findViewById(R.id.authorField);
//Nuevo Intent con Extras
Intent backData = new Intent();
backData.putExtra("body", quoteField.getText().toString());
backData.putExtra("author", authorField.getText().toString());
//Enviar la información
setResult(RESULT_OK, backData);
Luego recibes los datos con el método onActivityResult()
y realizas las operaciones necesarias para guardar los datos en la base de datos:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ADD_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
//Insertar registro en la base de datos
}
}
}
Hasta aquí hicimos un bosquejo rápido de la comunicación entre ambas actividades. Si te perdiste y aún no sabes como funcionan los Intents, entonces visita nuestro tutorial sobre Intents en Android para esclarecer mas este proceso de transmisión.
Crear una Base de Datos
Android SDK nos provee una serie de clases para administrar nuestro archivo de base de datos en SQLite. Normalmente cuando conectamos otro gestor de bases de datos tenemos que validar los datos del equipo, el usuario y el esquema, pero con SQLite no se requiere nada de eso, ya que podemos trabajar directamente sobre la base de datos.
La clase que nos permitirá comunicar nuestra aplicación con la base de datos se llama SQLiteOpenHelper
. Se trata de una clase abstracta que nos provee los mecanismos básicos para la relación entre la aplicación Android y la información. Solo tienes que derivar de ella una subclase que se ajuste a tus necesidades y tendrás un puente funcional hacia el archivo de datos.
Así que implementaremos una nueva clase que extienda de SQLiteOpenHelper
y la llamaremos QuotesReaderDbHelper
.
Veamos:
public class QuotesReaderDbHelper extends SQLiteOpenHelper {
public QuotesReaderDbHelper(Context context){
super(context,
DATABASE_NAME,//String name
null,//factory
DATABASE_VERSION//int version
);
}
@Override
public void onCreate(SQLiteDatabase db) {
//Crear la base de datos
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//Actualizar la base de datos
}
}
A continuación la explicación del código:
El constructor
Recibe el contexto con el cual se relacionará el helper. Luego invoca a super
, donde se envía el contexto y 3 parámetros adicionales:
name
: String que representa el nombre del archivo con extensión.db
donde se almacenará la base de datos, que a su vez corresponde al nombre de la base de datos.factory
: Asignamosnull
, por ahora no es necesario comprender el funcionamiento de este parámetro.version
: Entero que representa la versión de la base de datos. Su valor inicial por defecto es 1. Si en algún momento la versión es mayor se llama al métodoonUpgrade()
para actualizar la base de datos a la nueva versión. Si es menor, se llama adownUpgrade()
para volver a una versión previa.
onCreate()
Este método es llamado automáticamente cuando creamos una instancia de la clase SQLiteOpenHelper
. En su interior establecemos la creación de las tablas y registros. Recibe como parámetro una referencia de la clase SQLiteDataBase
, la cual representa el esquema completo de la base de datos. Por defecto el archivo de la base de datos será almacenado en la dirección /data/data/<paquete>/databases/<nombre-de-la-bd>.db
de nuestro dispositivo móvil.
onUpgrade()
Es ejecutado si se identificó que el usuario tiene una versión antigua de la base de datos. En su interior estableceremos instrucciones para modificar el esquema de la base de datos, como por ejemplo agregar una nueva tabla, añadir una nueva columna, etc. Recibe tres parámetros:
db
: Es la instancia deSQLiteDataBase
que representa la base de datos.oldVersion
: Se trata de un entero que indica la "Version antigua" de la base de datos.newVersion
: Entero que se refiere a la "Version Nueva" de la base de datos.
Crear un Script de la Base de Datos
La forma en que una base de datos esta estructurada(cantidad de tablas, registros, indices, etc) y el conjunto de convenciones para nombrar sus objetos se les llama Esquema. Por lo general el esquema inicial se guarda en un Script que nos permita recuperar las condiciones previas en cualquier momento.
Con SQLite no es diferente, por lo que debes crear un esquema predefinido para implementarlo a la hora de crear tu base de datos. La documentación de Android nos recomienda crear una clase llamada Contract Class. Esta clase guarda como constantes todas las características de la base de datos, ademas del código SQL necesario para la creación, inserción, actualización, etc.
El esquema de la aplicación Quotilius está basado en un diseño simple, el cual se compone de una tabla que llamarás Quotes
con tres atributos: _id
, cuerpo (body
) y autor (author
).
Ahora solo queda implementar ese esquema en una clase que llamaremos QuotesDataSource
. Veamos:
public class QuotesDataSource {
//Metainformación de la base de datos
public static final String QUOTES_TABLE_NAME = "Quotes";
public static final String STRING_TYPE = "text";
public static final String INT_TYPE = "integer";
//Campos de la tabla Quotes
public static class ColumnQuotes{
public static final String ID_QUOTES = BaseColumns._ID;
public static final String BODY_QUOTES = "body";
public static final String AUTHOR_QUOTES = "author";
}
//Script de Creación de la tabla Quotes
public static final String CREATE_QUOTES_SCRIPT =
"create table "+QUOTES_TABLE_NAME+"(" +
ColumnQuotes.ID_QUOTES+" "+INT_TYPE+" primary key autoincrement," +
ColumnQuotes.BODY_QUOTES+" "+STRING_TYPE+" not null," +
ColumnQuotes.AUTHOR_QUOTES+" "+STRING_TYPE+" not null)";
//Scripts de inserción por defecto
public static final String INSERT_QUOTES_SCRIPT =
"insert into "+QUOTES_TABLE_NAME+" values(" +
"null," +
"El ignorante afirma, el sabio duda y reflexiona," +
"Aristóteles)," +
"(null," +
"Hay derrotas que tienen mas dignidad que la victoria," +
"Jorge Luis Borges)," +
"(null," +
"Si buscas resultados distintos, no hagas siempre lo mismo," +
"Albert Einstein)," +
"(null," +
"Donde mora la libertad, allí está mi patria," +
"Benjamin Franklin)," +
"(null," +
"Ojo por ojo y todo el mundo acabará ciego," +
"Mahatma Gandhi)";
private QuotesReaderDbHelper openHelper;
private SQLiteDatabase database;
public QuotesDataSource(Context context) {
//Creando una instancia hacia la base de datos
openHelper = new QuotesReaderDbHelper(context);
database = openHelper.getWritableDatabase();
}
}
En el anterior código podemos notar los siguientes detalles:
- Creamos una constante para el nombre de la tabla llamada
QUOTES_TABLE_NAME
y otras dos para el tipo de datos que usaremos(STRING_TYPE
eINT_TYPE
). - Creamos la clase interna
ColumnQuotes
para guardar el nombre de las columnas de la tablaQuotes
. En el caso del campoID_QUOTES
usamos una constanteBaseColumns._ID
de Android con el valor"_id"
. - Creamos la constante
INSERT_QUOTES_INSERT
tipoString
para guardar una sentencia SQL para crear la tabla. - Creamos la constante
INSERT_QUOTES_SCRIPT
tipoString
para insertar todos los datos iniciales de nuestra tabla.
Como ves, es una clase muy completa que nos proporcionará flexibilidad al realizar operaciones sobre la base de datos. Estas declaraciones facilitan la adaptación del esquema si en algún momento cambian los datos de las tablas o columnas.
Adicional a todas estas definiciones tenemos dos variables privadas: openHelper
y database
. Ambas las usaremos en el constructor para acceder a la base de datos con el método getWritableDatabase()
, el cual retorna en una instancia de SQLiteDatabase
que nos permitirá leer y modificar la información de la base de datos directamente.
Crear la Base de Datos
Una vez terminado nuestro Esquema, procedemos a implementar los métodos onCreate()
y onUpgrade()
de nuestra clase QuotesReaderDbHelper
:
public class QuotesReaderDbHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "Quotes.db";
public static final int DATABASE_VERSION = 1;
public QuotesReaderDbHelper(Context context){
super(context,DATABASE_NAME,null,DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
//Crear la tabla Quotes
db.execSQL(QuotesDataSource.CREATE_QUOTES_SCRIPT);
//Insertar registros iniciales
db.execSQL(QuotesDataSource.INSERT_QUOTES_SCRIPT);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//Añade los cambios que se realizarán en el esquema
}
}
Lo primero que hicimos fue crear atributos de clase para el nombre de la base de datos y la versión. Para crear la tabla Quotes
llamamos al método execSQL()
de SQLiteDataBase
.
Este método ejecuta una sola sentencia SQL que no retorne en filas. Por lo que el comando SELECT
no es posible usarlo dentro de él.
Evita ejecutar múltiples sentencias en una sola invocación del método execSQL()
. Puede que se ejecute la primera, pero las otras no surtirán efecto.
Ahora solo queda por instancear un objeto QuotesDataSource
en Main
para que se cree nuestra base de datos:
//Crear nuevo objeto QuotesDataSource
dataSource = new QuotesDataSource(this);
Al ejecutar la aplicación se supone que la base de datos habrá sido creada con éxito en el sistema de archivos de Android. Pero...¿Como comprobar que así fue?...la respuesta la encontramos en el uso de las siguientes herramientas.
sqlite3
sqlite3 es una herramienta de administración para nuestras bases de datos SQLite a través de la linea de comandos. Normalmente puedes descargarla de la página oficial de SQLite, pero tanto como la distribución de Android y Android Studio ya la traen consigo.
Antes de ejecutarla en el dispositivo, primero usaremos la herramienta DDMS (Dalvik Debug Monitor Server) de Android SDK, la cual permite visualizar las características del dispositivo que se está ejecutando. En ella podemos visualizar estadísticas de rendimiento, monitorear recursos y navegar por el sistema de archivos.
Si deseas ejecutarla solo presiona el siguiente ícono en Android Studio:
Ahora dirígete a la pestaña "File Explorer"
Como ves, se visualizan todos los directorios que se encuentran en el dispositivo. Así que para ver si existe nuestro archivo de base de datos, iremos a la ruta de la cual hablamos al inicio /data/data/<paquete>/databases/Quotes.db
Si todo salió bien, veremos nuestro archivo Quotes.db
. Procede a guárdalo con el botón de la parte superior derecha denominado "Pull a file from the device". En la siguiente sección veremos algo interesante con él.
Ya que hemos comprobado que existe nuestra base de datos, iniciaremos sqlite3 dentro del dispositivo. Sigue los siguientes pasos:
Paso 1
Inicia el terminal de Windows (cmd
) o usa la pestaña "Terminal" de Android Studio:
Paso 2
Navega hasta el directorio platform-tools
del SDK de Android. En mi caso la dirección es: C:\Users\James\AppData\Local\Android\android-studio\sdk\platform-tools
. Recuerda que para navegar a través de carpetas en DOS se utiliza el comando cd
.
Paso 3
Una vez hayas encontrado el directorio, digita la siguiente linea de comandos:
adb shell
Este comando conecta remotamente la consola de comandos del dispositivo Android con tu consola local. Cuando ya estés conectado a la consola del AVD, verás en el terminal algo como esto:
root@android:/ #
Paso 4
Inicia sqlite3 en el dispositivo con el siguiente comando:
root@android:/ # sqlite3 data/data/TUPAQUETE/databases/Quotes.db
La anterior instrucción accede a sqlite3 y al mismo tiempo le pide que abra la base de datos expuesta en el directorio especificado. Si accedió a la base de datos verás los siguientes mensajes:
SQLite version 3.7.11 2012-03-20 11:35:50
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>
Paso 5
Usa el comando .schema
de sqlite3 para ver el resumen del esquema de la base de datos Quotes:
CREATE TABLE Quotes(_id integer primary key autoincrement,body text not null,author text not null);
CREATE TABLE android_metadata (locale TEXT);
El Esquema muestra que nuestra tabla Quotes
ha sido creada correctamente. La tabla llamada android_metadata
es parte de la metainformación de la base de datos, por lo que siempre la encontrarás.
Otra forma de comprobar el esquema de nuestra base de datos es usar sqlite3.exe
en nuestro equipo local.
Ve a la ruta para encontrar la carpeta platform-tools
del SDK de Android y ejecuta la aplicación. Luego usa .open
para abrir el archivo en una ruta especificada o copia y pega el archivo Quotes.db
en la carpeta:
sqlite>.open Quotes.db
Luego usa .schema
y averigua su contenido.
SQLite Browser
Si deseas conocer una herramienta mas visual, entonces SQLite Browser es una opción que te gustaría considerar. Se trata de una editor para archivos de bases de datos SQLite de código abierto y super sencillo de usar.
Solo basta con iniciarlo en tu pc y arrastrar el archivo "Quotes.db"
a su editor. Inmediatamente nos mostrará el esquema en forma de tablas con interfaz gráfica de usuario, ademas de permitirnos editar la estructura y ejecutar sentencias SQL dentro de ella.
CRUD
A continuación aprenderás a Leer, Insertar, Modificar y Eliminar registros. Recuerda que quién realiza este tipo de operaciones es la clase SQLiteDataBase
.
Insertar Registros
El método cuya funcionalidad es añadir filas a nuestras tablas se llama insert()
. Veamos un ejemplo:
//Nuestro contenedor de valores
ContentValues values = new ContentValues();
//Seteando body y author
values.put(QuotesDataSource.ColumnQuotes.BODY_QUOTES,"Nueva Frase");
values.put(QuotesDataSource.ColumnQuotes.AUTHOR_QUOTES,"Nuevo Autor");
//Insertando en la base de datos
database.insert(QUOTES_TABLE_NAME,null,values);
Lo primero que tenemos que hacer es crear un objeto del tipo ContentValues
, el cual permite almacenar una serie de datos(Valores) relacionados a una llave(Nombre de la columna). Donde cada elemento es añadido con el método put()
.
Luego invocamos a insert()
a través de la instancia de la base de datos. El primer parámetro que recibe es el nombre de la tabla. El segundo parámetro establece que si el objeto que contiene los valores está vacío, entonces no se debe insertar el registro en la base de datos. Y el tercer parámetro es nuestro objeto ContentValues
.
Con esta información ya puedes crear un método de guardado en la clase QuotesDataSource
para aislar la complejidad de la actividad Main. A dicho método le llamarás saveQuoteRow()
y recibirá el cuerpo de la frase y su autor:
public void saveQuoteRow(String body,String author){
//Nuestro contenedor de valores
ContentValues values = new ContentValues();
//Seteando body y author
values.put(QuotesDataSource.ColumnQuotes.BODY_QUOTES,body);
values.put(QuotesDataSource.ColumnQuotes.AUTHOR_QUOTES,author);
//Insertando en la base de datos
database.insert(QUOTES_TABLE_NAME,null,values);
}
Podrías usar el comando execSQL()
para ejecutar una sentencia INSERT
, pero como estás recibiendo datos externos, es mejor usar insert()
para evitar inyecciones SQL.
Consultar Registros
Para obtener los registros de nuestra tabla usaremos el método query()
.
query (String table,
String[] columns,
String selection,
String[] selectionArgs,
String groupBy,
String having,
String orderBy)
Por ejemplo, si quisiéramos consultar todos los datos de la tabla Quotes usaríamos el siguiente código:
Cursor c = db.query(
"Quotes", //Nombre de la tabla
null, //Lista de Columnas a consultar
null, //Columnas para la clausula WHERE
null, //Valores a comparar con las columnas del WHERE
null, //Agrupar con GROUP BY
null, //Condición HAVING para GROUP BY
null //Clausula ORDER BY
);
Este método te ayuda a añadir todas las partes posibles de las cuales se podría componer una consulta, además que te protege de inyecciones SQL, separando las clausulas de los argumentos. Ahora veamos el propósito de los parámetros:
table
: Nombre de la tabla a consultarcolumns
: Lista de nombres de las columnas que se van a consultar. Si deseas obtener todas las columnas usasnull
.selection
: Es el cuerpo de la sentenciaWHERE
con las columnas a condicionar. Es posible usar el placeholder'?'
para generalizar la condición.selectionArgs[]
: Es una lista de los valores que se usaran para reemplazar las incógnitas de selection en elWHERE
.groupBy
: Aquí puedes establecer como se vería la clausulaGROUP BY
, si es que la necesitas.having
: Establece la sentenciaHAVING
para condicionar agroupBy
.orderBy
: Reordena las filas de la consulta a través deORDER BY
.
Debido a la simplicidad de nuestra consulta anterior, la mayoría de parámetros fueron null
, ya que se consultan todas las columnas de la tabla y todos los registros. Pero si quisieras consultar solo el cuerpo de aquellos registros donde el autor sea "John D. Rockefeller" tendrías que usar la siguiente clausula WHERE
:
String columns[] = new String[]{ColumnQuotes.BODY_QUOTES};
String selection = ColumnQuotes.AUTHOR_QUOTES + " = ? ";//WHERE author = ?
String selectionArgs[] = new String[]{"John D. Rockefeller"};
Cursor c = db.query(
"Quotes",
columns,
selection,
selectionArgs,
null,
null,
null
);
Se usa el parámetro selection
para asignarle el nombre de la columna del cuerpo de la frase y luego en el parámetro selectionArgs
relacionas el valor a comparar.
Aprender más sobre la clausula WHERE
Ahora, existe otro método alternativo para realizar consultas llamado rawQuery()
. Con él debes crear un String
que contenga todo el código SQL de la consulta y lo pasamos como parámetro. Veamos:
database.rawQuery("select * from " + QUOTES_TABLE_NAME, null);
Si deseas crear una consulta generalizada usa el placeholder '?
' en la clausula WHERE
. Luego asignamos los valores a cada incógnita en el segundo parámetro:
String query = "select * from " + QUOTES_TABLE_NAME + "WHERE _id=?";
database.rawQuery(query, new String[]{"3"});
Tanto query()
como rawQuery()
retornan un objeto de tipo Cursor
. Este objeto es un apuntador al conjunto de valores obtenidos de la consulta. Al inicio el cursor apunta a una dirección previa a la primera fila. La idea es leer cada fila moviendo el cursor a la fila siguiente y así sucesivamente.
Emplea el método booleano moveToNext()
para recorrer el cursor. Su cometido es apuntar al siguiente elemento del cursor, el cual retorna en true
si fue posible o false
si ya no existen mas elementos. Este concepto es similar a cuando vimos cursores en SQL Server o en MySQL. Donde recorríamos cada elemento con un bucle while
hasta que ya no existieran mas elementos que referenciar.
Veamos:
while(c.moveToNext()){
String body = c.getString(ColomunQuotes.BODY_QUOTES);
//Acciones con el valor obtenido
}
Como ves, usamos métodos "get" para obtener el valor de cada columna a través del nombre o clave. Por ejemplo, al obtener el cuerpo de la frase usamos getString()
debido a que su tipo en la tabla es text
. Si fueses a obtener el código, entonces usas getInt()
.
Puedes aprovechar este nuevo concepto e implementar un nuevo método en la clase QuotesDataSource
llamado getAllQuotes()
. Su objetivo es retornar en un cursor todas las filas de la tabla Quotes
, lo que aísla la complejidad de la operación sobre la base de datos de la actividad Main:
public Cursor getAllQuotes(){
//Seleccionamos todas las filas de la tabla Quotes
return database.rawQuery(
"select * from " + QUOTES_TABLE_NAME, null);
}
Al igual que saveQuoteRow()
y getAllQuotes()
, puedes crear otros métodos para borrar, modificar o realiza consultas especificas. Tu objetivo siempre debe ser eliminar dependencias entre la actividad y el manejo de la base de datos.
Borrar Registros
Eliminar registros es muy sencillo, solo tenemos que usar el método delete()
. Recibe como parámetros el nombre de la tabla, el estilo de la selección de la clausula WHERE
y los valores de comparación para determinar que filas borrar.
Por ejemplo, eliminar la frase que tenga el código 3:
String selection = ColumnQuotes.BODY_QUOTES + " = ?";
String[] selectionArgs = { "3" };
db.delete("Quotes", selection, selectionArgs);
Actualizar Registros
En este caso usaremos el método update()
. Es exactamente el mismo estilo de uso que los anteriores métodos. Especificaremos el nombre de la tabla, los valores nuevos que vamos a usar y luego pondremos las cadenas de la instrucción where y sus argumentos comparativos:
//Nuestro contenedor de valores
ContentValues values = new ContentValues();
//Seteando body y author
values.put(QuotesDataSource.ColumnQuotes.BODY_QUOTES,"Nueva Frase");
values.put(QuotesDataSource.ColumnQuotes.AUTHOR_QUOTES,"Nuevo Autor");
//Clausula WHERE
String selection = ColumnQuotes.BODY_QUOTES + " = ?";
String[] selectionArgs = { "3" };
//Actualizando
database.update(QUOTES_TABLE_NAME, values, selection, selectionArgs);
La Clase CursorAdapter
Existe un adaptador especial para el manejo de bases de datos llamado CursorAdapter
. Esta clase permite poblar un ListView
a través de un cursor. Es decir, el origen de datos no será una lista ni un arreglo, si no un cursor.
CursorAdapter
es una clase abstracta de la cual se ha de crear tu adaptador personalizado. Con ArrayAdapter
teníamos que sobrescribir el método getView()
para inflar nuestras filas con los datos de la lista. Pero no es el caso con CursorAdapter
. Esta vez debemos sobrescribir dos métodos aislados llamados bindView()
y newView()
.
bindView()
es el encargado de poblar la lista con los datos del cursor y newView()
es quien infla cada view de la lista. Al implementar ambos métodos no debemos preocuparnos por iterar el curso, esto es manejado internamente.
Así que escribamos nuestra nueva clase QuotesAdapter
. En bindView()
simplemente debemos obtener los valores de las columnas y setearlos en los text views del layout. Y en newView()
accedemos a la instancia del LayoutInflater
a través del parent
y luego invocamos inflate()
para inflar nuestra fila. Observemos:
public class QuotesAdapter extends CursorAdapter {
public QuotesAdapter (Context context, Cursor cursor) {
super(context, cursor);
}
public void bindView(View view, Context context, Cursor cursor) {
TextView body = (TextView)view.findViewById(R.id.bodyText);
//Setear el texto del a columna en la posición 0 del cursor
body.setText(cursor.getString(cursor.getColumnIndex(0)));
TextView author = (TextView)view.findViewById(R.id.authorText);
author.setText(cursor.getString(cursor.getColumnIndex(1)));
}
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.list_item, null, false);
return view;
}
}
Ahora solo debemos cargar nuestra actividad principal e instancear nuestro adaptador con un cursor que apunte a todos los registros de nuestra tabla Quotes
.
adapter = new QuotesAdapter(this, sourceData.getAllQuotes());
setAdapter(adapter);
Por razones de compatibilidad en los procesos de manipulación de datos CursorAdapter
exige que uses en todas tus tablas una llave foránea cuya etiqueta sea "_id"
, si no lo haces se generará un error en tiempo de ejecución.
La Clase SimpleCursorAdapter
Si el diseño de las filas de tu lista es sencillo, entonces la clase SimpleCursorAdapter
podría ahorrarte tiempo de desarrollo. Esta clase es una extensión de CursorAdapter
que posee una implementación completa para los desarrolladores. Con ella no tenemos que sobrescribir métodos ni tampoco crear un archivo de diseño, ya que permite usar layouts del sistema.
Nuestra aplicación Quotilius tiene elementos sencillos, por que solo usamos dos text views para mostrar el cuerpo de la frase y el autor. Por esta razón podemos usar el layout del sistema two_line_list_item
. Fíjate como sería la creación de un nuevo adaptador :
//Iniciando el nuevo Adaptador
adapter = new SimpleCursorAdapter(
this,//Context context
android.R.layout.two_line_list_item,//int layout
dataSource.getAllQuotes(),//Cursor c
new String[]{ColumnQuotes.BODY_QUOTES,ColumnQuotes.AUTHOR_QUOTES},//String[] from
new int[]{android.R.id.text1, android.R.id.text2},//int[] to
SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER//int flags
);
setListAdapter(adapter);
Comprendamos un poco los parámetros:
context
: Es el contexto donde se encuentra la lista, en este caso es la misma actividad.layout
: Es el layour que usaremos para inflar los elementos de cada fila. En nuestro caso usamos el prefabricado por Android para los elementos con dos text viewsc
: Es el cursor que representa el origen de datos para el adaptador. Asignamos el cursor hacia todos los registros de la tablaQuotes
.from
: Es un arreglo de Strings que contiene el nombre de las columnas a consultar. Usamos los nombres de las columnas que contieneColumnQuotes
.to
: Es un arreglo de enteros con las referencias directas de los text views en el layout. Deben tener el mismo orden que las columnas. Los TextViews dentro detwo_line_list_item.xml
se llamantext1
ytext2
respectivamente.flags
: Es un bandera para establecer el comportamiento del adaptador.FLAG_REGISTER_CONTENT_OBSERVER
registra un observador adherido al cursor para saber cuando cambio su información y así refrescar la lista.
Finalmente completaremos el método onActivityResult()
para guardar el nuevo registro. Si la actividad Form
retorna los datos de forma correcta, entonces lo primero que debemos realizar es obtener los extras que trae el Intent
de regreso, luego insertamos el nuevo registro con el método saveQuoteRow()
y por último usamos el método changeCursor()
para actualizar el cursor y así refrescar los elementos de la lista:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ADD_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
//Insertando el registro con los datos del formulario
String body = data.getStringExtra("body");
String author = data.getStringExtra("author");
dataSource.saveQuoteRow(body,author);
//Refrescando la lista
adapter.changeCursor(dataSource.getAllQuotes());
}
}
}
Emplea las clases CursorLoader
y LoaderManager
para aislar las operaciones sobre la base de datos en otro hilo de ejecución. Operar la base de datos directamente sobre el hilo de UI puede producir problemas de rendimiento y visualización.