29737789 Android Content Provider Tutorial

23
Creación de un ContentProvider para el acceso a base de datos 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

Transcript of 29737789 Android Content Provider Tutorial

Page 1: 29737789 Android Content Provider Tutorial

Creación de un ContentProvider para el acceso a base de datos

IndexIntroducción...............................................................................................................................................2Crear un SQLiteOpenHelper......................................................................................................................2Crear el ContentProvider............................................................................................................................4

La URI identificando a nuestro ContentProvider..................................................................................7Inicializar el UriMatcher.......................................................................................................................8Implementar el método getType(...)......................................................................................................9El método onCreate(...)........................................................................................................................10Implementación métodos CRUD.........................................................................................................10

query(...)..........................................................................................................................................10insert().............................................................................................................................................11update()...........................................................................................................................................12delete()............................................................................................................................................13

Declarar el ContentProvider en nuestro AndroidManifest,xml................................................................14Utilización del ContentProvider dentro de nuestra actividad...................................................................15Editar el registro.......................................................................................................................................20

1

Page 2: 29737789 Android Content Provider Tutorial

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;

public class NamesSQLHelper extends SQLiteOpenHelper{

2

Page 3: 29737789 Android Content Provider Tutorial

Creación de un ContentProvider para el acceso a base de datos

public static final String DATABASE_NAME = "names.db";public static final int DATABASE_VERSION = 1;public static final String TABLE_NAMES ="names";public static final String FIELD_ID = "id";public static final String FIELD_NAME = "name";

public static final String PROJECTION_ALL_FIELDS[] = new String[]{FIELD_ID,FIELD_NAME

};public NamesSQLHelper(Context context) {

//Constructor que hay que llamar obligatoriamentesuper(context,DATABASE_NAME,null,DATABASE_VERSION);

}

@Overridepublic 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)");}

@Overridepublic 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

Page 4: 29737789 Android Content Provider Tutorial

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;

public class NamesContentProvider extends ContentProvider{

private static final String INVALID_URI_MESSAGE = "Invalid Uri: ";

4

Page 5: 29737789 Android Content Provider Tutorial

Creación de un ContentProvider para el acceso a base de datos

private static final String EQUALS = "=";public static final String AUTHORITY_PART ="org.examples.android";public static final int CODE_ALL_ITEMS = 1;public static final int CODE_SINGLE_ITEM = 2;public static final String CONTENT_PREFIX ="content://";public static final Uri CONTENT_URI = Uri.parse(CONTENT_PREFIX + AUTHORITY_PART +

"/names");public static final String MIME_TYPE_ALL_ITEMS

="vnd.android.cursor.dir/vnd.org.examples.android";public static final String MIME_TYPE_SINGLE_ITEM

="vnd.android.cursor.item/vnd.org.examples.android";public static final UriMatcher URI_MATCHER;private SQLiteDatabase database;private NamesSQLHelper dbHelper;

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);

}

@Overridepublic 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;

}

@Overridepublic 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

Page 6: 29737789 Android Content Provider Tutorial

Creación de un ContentProvider para el acceso a base de datos

throw new IllegalArgumentException(INVALID_URI_MESSAGE + uri);}

}

@Overridepublic 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;

}

@Overridepublic boolean onCreate() {

dbHelper = new NamesSQLHelper(getContext());database = dbHelper.getWritableDatabase();return database != null && database.isOpen();

}

@Overridepublic void onLowMemory() {

super.onLowMemory(); /* The database is closed if more memory is needed */

this.dbHelper.close();}

@Overridepublic 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;}

@Overridepublic 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

Page 7: 29737789 Android Content Provider Tutorial

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:

public static final Uri CONTENT_URI = Uri.parse(CONTENT_PREFIX + AUTHORITY_PART + "/names");

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

Page 8: 29737789 Android Content Provider Tutorial

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:

@Overridepublic 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

Page 9: 29737789 Android Content Provider Tutorial

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:

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:throw new IllegalArgumentException(INVALID_URI_MESSAGE + uri);

}}

9

Page 10: 29737789 Android Content Provider Tutorial

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:

public boolean onCreate() {dbHelper = new NamesSQLHelper(getContext());database = dbHelper.getWritableDatabase();return database != null && database.isOpen();

}

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:

@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[]

10

Page 11: 29737789 Android Content Provider Tutorial

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

Page 12: 29737789 Android Content Provider Tutorial

Creación de un ContentProvider para el acceso a base de datos

@Overridepublic 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

Page 13: 29737789 Android Content Provider Tutorial

Creación de un ContentProvider para el acceso a base de datos

@Overridepublic 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(). 

@Overridepublic 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

Page 14: 29737789 Android Content Provider Tutorial

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:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="org.examples.android" android:versionCode="1"android:versionName="1.0"><application android:icon="@drawable/icon" android:label="@string/app_name">

<activity android:name=".Names" android:label="@string/app_name"><intent-filter>

<action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" />

</intent-filter></activity><activity android:name=".NameEdit" android:label="@string/app_name">

<intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" />

</intent-filter></activity><provider android:name="org.examples.android.NamesContentProvider"

android:authorities="org.examples.android" /></application><uses-sdk android:minSdkVersion="4" />

</manifest>

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

Page 15: 29737789 Android Content Provider Tutorial

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(...).

@Overrideprotected 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 try­finally.

15

Page 16: 29737789 Android Content Provider Tutorial

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;

public class Names extends ListActivity {

private List<Name> nameList;private ArrayAdapter<Name> adapter;private static final int MENU_OPTION_ADD_OR_EDIT = 9;

public boolean onCreateOptionsMenu(Menu menu) {super.onCreateOptionsMenu(menu);

menu.add(0,MENU_OPTION_ADD_OR_EDIT,0,"Add").setIcon(android.R.drawable.ic_input_add);return true;

}

@Overridepublic 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

Page 17: 29737789 Android Content Provider Tutorial

Creación de un ContentProvider para el acceso a base de datos

@Overrideprotected 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);

}

@Overrideprotected 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:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical" android:layout_width="fill_parent"android:layout_height="fill_parent">

<!-- El layout de la lista se establece a traves del codigo de nuestra actividad, por eso nuestra lista debe tener un identificador predeterminado @+id/android:list -->

<ListView android:id="@+id/android:list" android:layout_width="fill_parent"android:layout_height="wrap_content"></ListView>

</LinearLayout>

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

Page 18: 29737789 Android Content Provider Tutorial

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.

@Overrideprotected 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

Page 19: 29737789 Android Content Provider Tutorial

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(...)

@Overridepublic 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

Page 20: 29737789 Android Content Provider Tutorial

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;

public class NameEdit extends Activity{@Overrideprotected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);this.setContentView(R.layout.edit);Button.class.cast(this.findViewById(R.id.Button01)).setOnClickListener(new

NameSaveListener(this));}

20

Page 21: 29737789 Android Content Provider Tutorial

Creación de un ContentProvider para el acceso a base de datos

@Overrideprotected 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:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent" android:layout_height="wrap_content"android:padding="10sp" android:orientation="vertical"><EditText android:id="@+id/EditText01" android:layout_width="fill_parent"

android:layout_height="wrap_content"></EditText><Button android:text="@string/save_button" android:id="@+id/Button01"

android:layout_width="fill_parent" android:layout_height="wrap_content"></Button></LinearLayout>

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

Page 22: 29737789 Android Content Provider Tutorial

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 class NameSaveListener implements OnClickListener {

private Activity activity;

public NameSaveListener(Activity a) {this.activity = a;

}

/* (non-Javadoc) * @see android.view.View.OnClickListener#onClick(android.view.View) */@Overridepublic 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

Page 23: 29737789 Android Content Provider Tutorial

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