Sei sulla pagina 1di 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
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
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 obligatoriamente
super(context,DATABASE_NAME,null,DATABASE_VERSION);
}

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

public class NamesContentProvider extends ContentProvider{

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

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

@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

throw new IllegalArgumentException(INVALID_URI_MESSAGE + uri);


}
}

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

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

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

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

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

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;

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

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

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

public class NameEdit extends Activity{


@Override
protected 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
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:

<?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
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)
*/
@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

Potrebbero piacerti anche