Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Index
Introducción...............................................................................................................................................2
Crear un SQLiteOpenHelper......................................................................................................................2
Crear el ContentProvider............................................................................................................................4
La URI identificando a nuestro ContentProvider..................................................................................7
Inicializar el UriMatcher.......................................................................................................................8
Implementar el método getType(...)......................................................................................................9
El método onCreate(...)........................................................................................................................10
Implementación métodos CRUD.........................................................................................................10
query(...)..........................................................................................................................................10
insert().............................................................................................................................................11
update()...........................................................................................................................................12
delete()............................................................................................................................................13
Declarar el ContentProvider en nuestro AndroidManifest,xml................................................................14
Utilización del ContentProvider dentro de nuestra actividad...................................................................15
Editar el registro.......................................................................................................................................20
1
Creación de un ContentProvider para el acceso a base de datos
Introducción
Antes de empezar a hablar sobre como realizar tu propio ContentProvider quiero recordar lo que dice la
documentación sobre los “Content Providers”:
“Content providers are one of the primary building blocks of Android applications, providing content
to applications. They encapsulate data and provide it to applications through the single
ContentResolver interface. A content provider is only required if you need to share data between
multiple applications. For example, the contacts data is used by multiple applications and must be
stored in a content provider. If you don't need to share data amongst multiple applications you can use
a database directly via SQLiteDatabase. “
Quiero resaltar sobre todo lo que se refiere al hecho que para acceder a una base de datos NO es
necesario crear un ContentProvider. Un ContentProvider está pensado para que varias aplicaciones
accedan a un mismo repositorio de datos a través de él.
Esto no está nada claro en ninguno de los manuales que he leido sobre Android, la mayoría utilzan un
ContentProvider para acceder a base de datos y al no explicarte su naturaleza siempre hay detalles que
no se explican.
Aunque en el ejemplo que voy a realizar vuelvo a caer en el tópico de acceder a una base de datos a
través de un ContentProvider que solo una aplicación utiliza, conste que lo hago solo con fines
didacticos.
Crear un SQLiteOpenHelper
Lo primero que debemos hacer es crear una clase que extienda de SQLiteOpenHelper. Esta clase
ayudará al ContentProvider a la hora de conectar con la base de datos, y en último caso modificar el
esquema de la base de datos.
Siempre que utilicemos nuestro ContentProvider esté utilizará el SQLiteOpenHelper para establecer
una conexión con la base de datos.
package org.examples.android;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
2
Creación de un ContentProvider para el acceso a base de datos
@Override
public void onCreate(SQLiteDatabase db) {
/* Esto se ejecutara solo si se va a crear la base de datos. Si solo se
* va a acceder a ella no se ejecutara */
db.execSQL("CREATE TABLE " + TABLE_NAMES +
" (" + FIELD_ID +" INTEGER PRIMARY KEY AUTOINCREMENT," + FIELD_NAME
+" text)");
}
@Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
// De momento no vamos a actualizar la base de datos
}
Cuando se programa en Android es muy habitual utilizar variables estáticas finales para albergar
nombres y objetos que no van a cambiar en toda la vida de la aplicación y que son susceptibles de
utilizarse intensivamente, como nombres de campos de base de datos, nombre de tablas, urls de
recursos...etc.
En esta clase vemos como en el constructor llamamos al constructor de la clase padre para establecer
que la base de datos a la que queremos acceder es names.db y la versión de la base de datos es la 1.
Estos datos servirán a la clase tanto para establecer una conexión con la base de datos como para en
último caso crearla programáticamente.
A continuación vemos el método onCreate(...) que es llamado cuando se crea una instancia de esta clase
y que puede ser utilizado para modificar el esquema de la base de datos. En el ejemplo se utiliza para
crear la estructura de la tabla del ejemplo. También existe el método onUpgrade(...) que puede ser
utilizado para actualizar el esquema de la base de datos en versiones posteriores de la aplicación.
Pero ¿Cómo se crea la base de datos?
Bien, hay dos formas de hacerlo:
• La primera es utilizando la herramienta adb que viene en las herramientas del SQK de Android.
3
Creación de un ContentProvider para el acceso a base de datos
Puedes crear una shell (escribiendo “adb shell” en linea de comandos) mientras el emulador está
funcionando e ir al directorio /data/data/eldirectoriodetuproyecto y crear un nuevo directorio que
se llame databases. Una vez dentro del directorio databases se puede crear una nueva base de
datos utilizando el comando sqlite3 nombredelabasededatos.db. Si todo fue correctamente
aparecerá el promtp de la consola de sqlite3. Se sale de la consola escribiendo “.exit”. Además
de crear la base de datos debes darle permisos al directorio databases ya que cuando entras en la
consola entras como root y tu aplicación no tendrá esos privilegios, por eso debes situarte por
encima de la carpeta databases y ejecutar “chmod 777 databases”.
• La segunda forma, que recomiendo es crear la base de datos programáticamente como vamos a
ver a continuación.
Crear el ContentProvider
Antes de crear el ContentProvider se deben tener en cuenta una serie de conceptos:
• Todos los ContentProvider se localizan en base a URIs. Por esa razón se recomienda exponer en
una variable estática final la URI del ContentProvider para poder acceder a ella posteriormente.
• Aunque los ContentProvider pueden ser utilizados para acceder a cualquier tipo de datos su
diseño parece haber sido pensado para acceder a bases de datos relacionales. Esto es debido al
interface Cursor que tiene un parecido razonable al archiconocido java.sql.ResultSet de JavaSE.
Merece la pensa echarle un vistazo a la documentación de Android sobre el interface Cursor
para familiarizarse con él.
En general la estructura de un ContentProvider se ha pensado para realizar las acciones básicas de
CRUD (Create, Read, Update and Delete) a través de los métodos: insert,query,update, delete.
Después de esta breve introducción sobre algunos de los conceptos que rodean los ContentProvider
veamos el código de ejemplo:
package org.examples.android;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
4
Creación de un ContentProvider para el acceso a base de datos
static{
URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
URI_MATCHER.addURI(AUTHORITY_PART,"names",CODE_ALL_ITEMS);
URI_MATCHER.addURI(AUTHORITY_PART,"names/#",CODE_SINGLE_ITEM);
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
int rowsAffected = 0;
switch(URI_MATCHER.match(uri)){
case CODE_ALL_ITEMS:
rowsAffected =
this.getOrOpenDatabase().delete(NamesSQLHelper.TABLE_NAMES, where, whereArgs);
break;
case CODE_SINGLE_ITEM:
String singleRecordId = uri.getPathSegments().get(1);
rowsAffected = this.getOrOpenDatabase().delete(
NamesSQLHelper.TABLE_NAMES,
NamesSQLHelper.FIELD_ID + EQUALS + singleRecordId,
whereArgs);
break;
default:
throw new IllegalArgumentException(INVALID_URI_MESSAGE + uri);
}
return rowsAffected;
}
/**
* @return
*/
private SQLiteDatabase getOrOpenDatabase(){
SQLiteDatabase db = null;
if (this.database!= null && database.isOpen()){
db = this.database;
} else {
db = dbHelper.getWritableDatabase();
}
return db;
}
@Override
public String getType(Uri uri) {
switch (URI_MATCHER.match(uri)) {
case CODE_ALL_ITEMS:
return MIME_TYPE_ALL_ITEMS;
case CODE_SINGLE_ITEM:
return MIME_TYPE_SINGLE_ITEM;
default:
5
Creación de un ContentProvider para el acceso a base de datos
@Override
public Uri insert(Uri arg0, ContentValues arg1) {
long rowID =
getOrOpenDatabase().insert(NamesSQLHelper.TABLE_NAMES,NamesSQLHelper.DATABASE_NAME,arg1);
Uri newRecordUri = null;
switch(URI_MATCHER.match(arg0)){
case CODE_ALL_ITEMS:
if (rowID > 0){
newRecordUri =
ContentUris.withAppendedId(CONTENT_URI,rowID);
}
break;
default:
throw new IllegalArgumentException(INVALID_URI_MESSAGE + arg0);
}
return newRecordUri;
}
@Override
public boolean onCreate() {
dbHelper = new NamesSQLHelper(getContext());
database = dbHelper.getWritableDatabase();
return database != null && database.isOpen();
}
@Override
public void onLowMemory() {
super.onLowMemory();
/* The database is closed if more memory is needed */
this.dbHelper.close();
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[]
selectionArgs,String sort) {
Cursor cursor = null;
SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
switch (URI_MATCHER.match(uri)) {
case CODE_SINGLE_ITEM:
String id = uri.getPathSegments().get(1);
qBuilder.appendWhere(NamesSQLHelper.FIELD_ID + EQUALS + id);
break;
case UriMatcher.NO_MATCH:
throw new IllegalArgumentException(INVALID_URI_MESSAGE+uri);
}
qBuilder.setTables(NamesSQLHelper.TABLE_NAMES);
cursor =
qBuilder.query(getOrOpenDatabase(),projection,selection,selectionArgs,null,null,null);
return cursor;
}
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
int rowsAffected = 0;
String recordId = null;
switch (URI_MATCHER.match(uri)) {
case CODE_ALL_ITEMS:
6
Creación de un ContentProvider para el acceso a base de datos
rowsAffected =
getOrOpenDatabase().update(NamesSQLHelper.TABLE_NAMES, values, where, whereArgs);
break;
case CODE_SINGLE_ITEM:
recordId = uri.getPathSegments().get(1);
rowsAffected = getOrOpenDatabase().update(
NamesSQLHelper.TABLE_NAMES,
values,
NamesSQLHelper.FIELD_ID + EQUALS + recordId,
whereArgs);
break;
default:
throw new IllegalArgumentException(INVALID_URI_MESSAGE+uri);
}
return rowsAffected;
}
}
Bueno, menudo “tocho” ¿no?
La URI identificando a nuestro ContentProvider
Vamos por partes:
Lo primero que hay que hacer a la hora de crear un ContentProvider es establecer la URI para poder
localizar el ContentProvider:
El uso de URIs se ha pensado para que cualquier recurso, ya sea en base de datos, o en cualquier otro
contenedor pueda ser identificado. ¿Esto que significa? Pues esto significa que cuando queramos
acceder por ejemplo al nombre con identificador 3 accedamos a él con una url del tipo
“content://org.examples.android/names/1”. Esta URI identifica al nombre con identificador 1. Digamos
que la variable CONTENT_URI servirá como raiz de todos los recursos que se puedan localizar a
través de este ContentProvider.
Esta URI esta formada por:
• el prefijo del protocolo “content://”
• la autoridad “org.examples.android”: Este fragmento deberá utilizarse en la declaración del
AndroidManifest.xml que veremos más adelante.
• el recurso “/names”: en este caso estariamos pidiendo todos los nombres
7
Creación de un ContentProvider para el acceso a base de datos
Inicializar el UriMatcher
Después de crear la URI raiz debemos crear un UriMatcher. Este objeto nos servirá en nuestro
ContentProvider para poder analizar las URIs de los recursos que se piden y poder actuar en
consecuencia. Por ejemplo cuando desde nuestra Activity queramos eliminar un recurso utilizaremos
una sentencia parecida a esta:
getContentResolver().delete(URI_DEL_RECURSO_QUE_SE_QUIERE_BORRAR,where,whereArgs);
En este caso nuestro método delete dentro del ContentProvider deberá analizar esa URI y decidir que
hacer al respecto. En nuestro caso:
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
int rowsAffected = 0;
switch(URI_MATCHER.match(uri)){
case CODE_ALL_ITEMS:
rowsAffected =
this.getOrOpenDatabase().delete(NamesSQLHelper.TABLE_NAMES, where, whereArgs);
break;
case CODE_SINGLE_ITEM:
String singleRecordId = uri.getPathSegments().get(1);
rowsAffected = this.getOrOpenDatabase().delete(
NamesSQLHelper.TABLE_NAMES,
NamesSQLHelper.FIELD_ID + EQUALS + singleRecordId,
whereArgs);
break;
default:
throw new IllegalArgumentException(INVALID_URI_MESSAGE + uri);
}
return rowsAffected;
}
Como vemos utilizando nuestro UriMatcher se decide que si la uri identifica a todos los recursos, todos
los registros se borrarán, mientras que si la uri identifica a un recurso en particular solo este se borrará.
Normalmente el objeto UriMatcher se inicializa en un bloque estático ya que va a ser utilizado durante
toda la vida del ContentProvider y no va a cambiar. De este modo también se optimizan recursos:
8
Creación de un ContentProvider para el acceso a base de datos
static{
URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
URI_MATCHER.addURI(AUTHORITY_PART,"names",CODE_ALL_ITEMS);
URI_MATCHER.addURI(AUTHORITY_PART,"names/#",CODE_SINGLE_ITEM);
}
Cuando se inicializa nuestro UriMatcher se le van agregando todas aquellas URIs susceptibles de ser
manejadas por nuestro ContentProvider. En este caso se crea la instancia agregando la URI
NO_MATCH. A continuación se agregan las URIs de los recursos que vamos a manejar. MUY
IMPORTANTE fijarse en que las URIs que agregamos solamente identifican la parte de la autoridad y
los segmentos posteriores, NO se utiliza el protocolo para estas URIs. Se han agregado 2 tipos de URIs
• La URI que identificará todos los registros
(URI_MATCHER.addURI(AUTHORITY_PART,"names",CODE_ALL_ITEMS);)
• La URI que identificará un registro en concreto
(URI_MATCHER.addURI(AUTHORITY_PART,"names/#",CODE_SINGLE_ITEM);)
El método addURI agrega una nueva URI que tiene como raiz la parte de la autoridad y como parte
única un segmento (“names” o “names/#”), por último se establece un código deberá devolver el
método UriMatcher.match(Uri uri) cuando identifique esta URI.
Implementar el método getType(...)
Lo siguiente es establecer los MIME types que vamos a manejar en nuestro ContentProvider
implementando el método getType(). En general existe una convención sobre como establecer los mime
types distinguiendo entre un recurso único y una collección de recursos:
• Colecciones: “vnd.parteautoridad.dir/vnd.parteautoridad”
• Recursos: “vnd.parteautoridad.item/vnd.parteautoridad”
De este modo tendriamos el código del método:
9
Creación de un ContentProvider para el acceso a base de datos
El método onCreate(...)
Ahora antes de implementar los métodos que nos permitirán acceder a la base de datos nos centraremos
un momento en el método onCreate(...) de nuestro ContentProvider:
En este metodo inicializamos nuestra especialización de la clase SQLiteOpenHelper y obtenemos una
instancia de la base de datos que nos permite tanto leer como escribir en ella. El método onCreate(...)
debe devolver true si la base de datos existe y se ha podido abrir, false en caso contrario.
Implementación métodos CRUD
Ya llegamos a la “chicha” del asunto y es el acceso a la base de datos para insertar, consultar, actualizar
y borrar registros.
query(...)
El método query recibe como parametros:
• La uri que identifica el recurso que se quiere obtener
• Un array de String con los nombres de los campos que se desean obtener
• Un array con las condiciones a esa consulta (del tipo {“id”,”name”}
• Los valores para cada una de las condiciones
• Una expresión de ordenación (“name desc” por ejemplo)
En nuestro ejemplo tenemos la siguiente implementación:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[]
10
Creación de un ContentProvider para el acceso a base de datos
selectionArgs,String sort) {
Cursor cursor = null;
SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
switch (URI_MATCHER.match(uri)) {
case CODE_SINGLE_ITEM:
String id = uri.getPathSegments().get(1);
qBuilder.appendWhere(NamesSQLHelper.FIELD_ID + EQUALS + id);
break;
case UriMatcher.NO_MATCH:
throw new IllegalArgumentException(INVALID_URI_MESSAGE+uri);
}
qBuilder.setTables(NamesSQLHelper.TABLE_NAMES);
cursor =
qBuilder.query(getOrOpenDatabase(),projection,selection,selectionArgs,null,null,null);
return cursor;
}
Como se puede ver en este caso se ignoran todos los parametros menos la uri y el array con los campos
que queremos obtener. Esto es porque en la uri que recibe el método hay toda la información necesaria
para realizar la consulta. Pongamos por ejemplo que recibe la siguiente uri:
content://org.examples.android/names/22
Esta uri nos indica que queremos el nombre con el identificador numero 22.
Para realizar la consulta contra la base de datos nos ayudamos de la clase SQLiteQueryBuilder que
sirve para realizar consultas de manera programática. En este ejemplo cuando nos llega una uri de un
registro en concreto (CODE_SINGLE_ITEM) agregamos una clausula where sobre el identificador del
registro recuperando el identificador de la uri, en caso de que sea una uri para recuperar todos los
registros no se agrega ninguna clausula where.
Para que podamos realizar la consulta debemos decirle a la clase SQLiteQueryBuilder sobre qué tablas
queremos realizar la consulta (En este caso solo una “names”)
Finalmente recuperamos el cursor con el resultado de la llamada al método query de la clase
SQLiteQueryBuilder. Ya solo nos quedaría recorrer ese cursor para recuperar los datos.
insert()
El método insert debe devolver la URI del registro que se ha insertado en la base de datos. En nuestro
ejemplo está implementado de la siguiente manera:
11
Creación de un ContentProvider para el acceso a base de datos
@Override
public Uri insert(Uri arg0, ContentValues arg1) {
long rowID =
getOrOpenDatabase().insert(NamesSQLHelper.TABLE_NAMES,NamesSQLHelper.DATABASE_NAME,arg1);
Uri newRecordUri = null;
switch(URI_MATCHER.match(arg0)){
case CODE_ALL_ITEMS:
if (rowID > 0){
newRecordUri =
ContentUris.withAppendedId(CONTENT_URI,rowID);
}
break;
default:
throw new IllegalArgumentException(INVALID_URI_MESSAGE + arg0);
}
return newRecordUri;
}
Se utiliza la instancia de la clase SQLiteDatabase (en este caso nos la devuelve el método
getOrOpenDatabase()), que nos devuelve, si no hay ningún problema, el identificador del nuevo registro
insertado. En esta implementación hemos decidido que si la uri con la que se está intentando insertar el
nuevo registro no corresponde al código CODE_ALL_ITEMS se lance una excepción. En caso de que
la URI sea válida entonces construimos la URI del recurso a partir de la URI raiz (CONTENT_URI)
más el identificador del nuevo registro.
La verdad es que está mal la implementación en cuanto a que se debería comprobar la validez de la URI
antes de insertar el registro en la base de datos, pero bueno, es un ejemplo.
Merece una mención especial el objeto ContentValues, ya que sirve como contenedor de valores antes
de pasarlos al método insert de la instancia de SQLiteDatabase.
update()
Tanto el método update como el método delete devuelven el numero de registros afectados por la
ejecución del método.
En el caso de update este método recibe los siguientes parametros:
• La uri que identifica el recurso que se quiere actualizar
• Los valores que se quieren actualizar (a través del objeto ContentValues)
• Las condiciones de la actualización
• Los valores de esas condiciones
12
Creación de un ContentProvider para el acceso a base de datos
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
int rowsAffected = 0;
String recordId = null;
switch (URI_MATCHER.match(uri)) {
case CODE_ALL_ITEMS:
rowsAffected =
getOrOpenDatabase().update(NamesSQLHelper.TABLE_NAMES, values, where, whereArgs);
break;
case CODE_SINGLE_ITEM:
recordId = uri.getPathSegments().get(1);
rowsAffected = getOrOpenDatabase().update(
NamesSQLHelper.TABLE_NAMES,
values,
NamesSQLHelper.FIELD_ID + EQUALS + recordId,
whereArgs);
break;
default:
throw new IllegalArgumentException(INVALID_URI_MESSAGE+uri);
}
return rowsAffected;
}
}
Como siempre distinguimos entre el tipo de URI que nos llega y decidimos si vamos a actualizar todos
los registros de la tabla “names” con los valores que contenga el objeto ContentValues o si solo vamos
a modificar un solo registro con esos valores.
En el caso de que sea un solo registro, ejecutamos la acción de actualización sobre ese registro
agregando una condición (NamesSQLHelper.FIELD_ID + EQUALS + recordId) para que solo se actualice ese
registro.
delete()
Por último el método delete sigue la misma estructura que el resto de método vistos con anterioridad:
identificamos el tipo de recurso que se quiere borrar a través de la Uri y actuamos en consecuencia
utilizando la instancia de la clase SQLiteDatabase que obtenemos a través del método
getOrOpenDatabase().
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
int rowsAffected = 0;
switch(URI_MATCHER.match(uri)){
case CODE_ALL_ITEMS:
rowsAffected =
this.getOrOpenDatabase().delete(NamesSQLHelper.TABLE_NAMES, where, whereArgs);
break;
case CODE_SINGLE_ITEM:
String singleRecordId = uri.getPathSegments().get(1);
13
Creación de un ContentProvider para el acceso a base de datos
rowsAffected = this.getOrOpenDatabase().delete(
NamesSQLHelper.TABLE_NAMES,
NamesSQLHelper.FIELD_ID + EQUALS + singleRecordId,
whereArgs);
break;
default:
throw new IllegalArgumentException(INVALID_URI_MESSAGE + uri);
}
return rowsAffected;
}
Al final del método devolvemos el numero de registros afectados.
Declarar el ContentProvider en nuestro AndroidManifest,xml
Si queremos utilizar nuestro ContentProvider en nuestra aplicación debemos declararlo en nuestro
AndroidManifest.xml. Asi es como está descrito en nuestra aplicación de ejemplo:
En este caso vemos que nuestra aplicación tiene dos Activity y un ContentProvider. La declaración del
ContentProvider requiere que se especifiquen los siguientes atributos:
• android:name: Este atributo debe mostrar el nombre cualificado de la clase de nuestro
ContentProvider
• android:authority: Este atributo representa el paquete dentro del cual se permite la utilización de
14
Creación de un ContentProvider para el acceso a base de datos
nuestro ContentProvider.
Utilización del ContentProvider dentro de nuestra actividad
Una vez hemos creado y declarado nuestro ContentProvider es hora de utilizarlo en nuestras pantallas.
En la pantalla inicial vamos a mostrar un listado con todos los nombres que hemos creado hasta la fecha
(Esta claro que la primera vez que arranquemos la aplicación no se mostrará ninguno).
Es importante tener en cuenta a la hora de mostrar información dentro de una pantalla qué métodos se
deben utilizar para realizar las labores de acceso a la base de datos. En nuestro caso vamos a utilizar el
método onResume(...).
@Override
protected void onResume() {
Cursor nameListCursor = null;
try{
nameListCursor = this.getContentResolver().
query(NamesContentProvider.CONTENT_URI,
NamesSQLHelper.PROJECTION_ALL_FIELDS, null,null,null);
this.nameList.clear();
if (nameListCursor!= null && nameListCursor.moveToFirst()){
do{
int id =
nameListCursor.getInt(nameListCursor.getColumnIndex(NamesSQLHelper.FIELD_ID));
String name =
nameListCursor.getString(nameListCursor.getColumnIndex(NamesSQLHelper.FIELD_NAME));
nameList.add(new Name(id, name));
} while (nameListCursor.moveToNext());
}
this.adapter.notifyDataSetChanged();
} finally{
if (nameListCursor!=null){
nameListCursor.close();
}
}
super.onResume();
}
Si necesitamos utilizar cualquier objeto a lo largo de la vida de nuestra Activity podemos inicializarlo
en el método onCreate(...). Nuestro ContentProvider no necesita ser inicializado en la Activity ya que
sigue su propio ciclo de vida. Lo único que tenemos que hacer es llamarlo a través del método
getContentResolver() de la clase Activity.
Al igual que hay que recordar siempre en JDBC cerrar las conexiones una vez terminada la consulta
aqui debemos tener especial atención a cerrar los cursores que abrimos. En nuestro caso lo hemos
hecho a través de una clausula tryfinally.
15
Creación de un ContentProvider para el acceso a base de datos
El método onResume() se ejecutará siempre que nuestra pantalla (clase Activity) vuelva a tener el foco
de la aplicación. De esta manera cada vez que aparezca esta pantalla se realizará una consulta a la base
de datos para traerse todos los nombres que haya en la base de datos.
En nuestro caso para mostrar los nombres en pantalla solo tenemos que agregarlos a una instancia de
tipo java.util.List que está asociada a una ListView a través de un ArrayProvider inicializado en el
método onCreate(...). El código de la actividad completa es la siguiente:
package org.examples.android;
import java.util.ArrayList;
import java.util.List;
import android.app.ListActivity;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
menu.add(0,MENU_OPTION_ADD_OR_EDIT,0,"Add").setIcon(android.R.drawable.ic_input_add);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent editIntent = null;
switch(item.getItemId()){
case MENU_OPTION_ADD_OR_EDIT:
editIntent = new Intent(this,NameEdit.class);
break;
}
if (editIntent != null){
startActivityForResult(editIntent, 1);
}
return true;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.main);
this.nameList = new ArrayList<Name>();
this.adapter = new
ArrayAdapter<Name>(this,android.R.layout.simple_list_item_1,nameList);
this.setListAdapter(adapter);
}
16
Creación de un ContentProvider para el acceso a base de datos
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Intent editIntent = new Intent(this,NameEdit.class);
editIntent.putExtra(NamesSQLHelper.FIELD_ID,
this.nameList.get(position).getId());
this.startActivity(editIntent);
}
@Override
protected void onResume() {
Cursor nameListCursor = null;
try{
nameListCursor = this.getContentResolver().
query(NamesContentProvider.CONTENT_URI,
NamesSQLHelper.PROJECTION_ALL_FIELDS, null,null,null);
this.nameList.clear();
if (nameListCursor!= null && nameListCursor.moveToFirst()){
do{
int id =
nameListCursor.getInt(nameListCursor.getColumnIndex(NamesSQLHelper.FIELD_ID));
String name =
nameListCursor.getString(nameListCursor.getColumnIndex(NamesSQLHelper.FIELD_NAME));
nameList.add(new Name(id, name));
} while (nameListCursor.moveToNext());
}
this.adapter.notifyDataSetChanged();
} finally{
if (nameListCursor!=null){
nameListCursor.close();
}
}
super.onResume();
}
}
Y su layout es el siguiente:
Lo primero que hay que tener en cuenta es que esta actividad es del tipo ListActivity ya que el elemento
principal de la pantalla es la lista que quiero manejar. Esta clase tiene una serie de métodos especificos
para trabajar con listas.
Como se puede observar en nuestra actividad hemos creado un menu para crear nuevos nombres
17
Creación de un ContentProvider para el acceso a base de datos
(onCreateOptionsMenu y onOptionsItemSelected) y también hemos agregado un método para
escuchar los elementos de la lista que se pulsan para editarlos posteriormente en otra actividad
(onListItemClick).
Ya que nuestra lista es sencilla y solo quiere mostrar el nombre simplemente crearemos un
ArrayAdapter en el cual le indicamos:
• El contexto: en este caso la propia actividad (this)
• El layout que va a utilizar la lista: En este caso le indicamos una por defecto de android
(R.layout.simple_list_item_1). Si quisieramos que cada elemento se renderizara de manera
diferente deberiamos implementar nuestro propio ArrayAdapter además de crear un layout
nuevo.
• La lista que contiene los datos: En nuestro caso una instancia de java.util.ArrayList
Cuando pulsemos sobre uno de los nombres de la lista vamos a lanzar una nueva actividad a la que
vamos a pasar el identificador del registro para que se pueda editar.
El método que esta “escuchando” cualquier pulsación sobre los elementos del menu es el método
onListItemClick() de la clase ListActivity.
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Intent editIntent = new Intent(this,NameEdit.class);
editIntent.putExtra(NamesSQLHelper.FIELD_ID,
this.nameList.get(position).getId());
18
Creación de un ContentProvider para el acceso a base de datos
this.startActivity(editIntent);
}
También abriremos la misma actividad pero sin pasarle ningún identificador cuando pulsemos sobre la
opción de menu “Add”.
La creación del menu se hace a través del método onCreateOptionsMenu(...) y el método que se
encarga de establecer las acciones dependiendo de la opción del menu pulsada es
onOptionsItemSelected(...)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent editIntent = null;
switch(item.getItemId()){
case MENU_OPTION_ADD_OR_EDIT:
editIntent = new Intent(this,NameEdit.class);
break;
}
if (editIntent != null){
startActivityForResult(editIntent, 1);
}
return true;
}
Como vemos si hemos pulsado sobre el boton de agregar/editar (es el mismo) creamos un Intent y lo
arrancaremos a través del método startActivityForResult(...).
19
Creación de un ContentProvider para el acceso a base de datos
Editar el registro
La pantalla de edición es muy básica:
El código de la actividad que va a crear un nuevo registro o a editar uno ya existente es la siguiente:
package org.examples.android;
import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
20
Creación de un ContentProvider para el acceso a base de datos
@Override
protected void onResume() {
Bundle bundle = this.getIntent().getExtras();
int id = bundle!= null ? bundle.getInt(NamesSQLHelper.FIELD_ID) : -1;
if (id > 0){
Cursor cursor = null;
try{
cursor = this.getContentResolver().query(
Uri.withAppendedPath(NamesContentProvider.CONTENT_URI, String.valueOf(id)),
NamesSQLHelper.PROJECTION_ALL_FIELDS,
null,null,null);
if (cursor.moveToFirst()){
String name =
cursor.getString(cursor.getColumnIndex(NamesSQLHelper.FIELD_NAME));
EditText.class.cast(this.findViewById(R.id.EditText01)).setText(name);
}
} finally{
cursor.close();
}
}
super.onResume();
}
}
Con su layout correspondiente:
Es una pantalla con un campo de texto editable y un botón para salvar el registro. Como vemos
utilizamos el método onResume() para recuperar el registro solamente en el caso de que se le haya
pasado a la actividad el identificador del registro.
También en el método onCreate(...) asociamos un listener al boton de salvar para realizar la acción de
guardar el registro en la base de datos, el código del listener es el siguiente:
package org.examples.android;
21
Creación de un ContentProvider para el acceso a base de datos
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
public NameSaveListener(Activity a) {
this.activity = a;
}
/* (non-Javadoc)
* @see android.view.View.OnClickListener#onClick(android.view.View)
*/
@Override
public void onClick(View view) {
ContentValues values = new ContentValues();
Bundle bundle = this.activity.getIntent().getExtras();
int id = bundle!=null ? bundle.getInt(NamesSQLHelper.FIELD_ID) : -1;
String name =
EditText.class.cast(this.activity.findViewById(R.id.EditText01)).getText().toString(); ;
Cursor cursor = null;
if (id > 0){
try{
cursor = this.activity.getContentResolver().query(
Uri.withAppendedPath(NamesContentProvider.CONTENT_URI,String.valueOf(id)),
NamesSQLHelper.PROJECTION_ALL_FIELDS,
null,null,null);
if (cursor.moveToFirst()){
values.put(NamesSQLHelper.FIELD_ID,id);
values.put(NamesSQLHelper.FIELD_NAME,name);
int rowsAffected =
this.activity.getContentResolver().update(
Uri.withAppendedPath(NamesContentProvider.CONTENT_URI,String.valueOf(id)),
values,null,null);
if (rowsAffected <= 0){
Log.w("Database","No row affected");
}
}
} finally {
cursor.close();
}
} else {
values.put(NamesSQLHelper.FIELD_NAME,name);
this.activity.getContentResolver().insert(NamesContentProvider.CONTENT_URI,
values);
}
this.activity.finish();
}
}
Vemos como en el caso de que el registro exista el registro lo actualizamos y en caso de que no exista
22
Creación de un ContentProvider para el acceso a base de datos
realizamos una nueva inserción en la base de datos. Le hemos pasado al listener la instancia de la
actividad para poder recuperar el identificador del registro en el caso de que este se estuviera editando.
También nos sirve para recuperar la instancia del campo de texto y poder recuperar asi el valor
introducido (Aunque para esto también nos sirve la instancia “view” que se pasa como argumento al
método onClick(View view).
Cuando se termina de editar o insertar el registro finaliza la actividad volviendo a la anterior pantalla.
Espero que este pequeño tutorial te haya servido para aprender algo sobre como crear tu propio
ContentProvider de acceso a base de datos. No obstante espero vuestras corrección y comentarios en
http://desmontandojava.blogspot.com/
23