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

static static static static static

final final final final final

String DATABASE_NAME = "names.db"; int DATABASE_VERSION = 1; String TABLE_NAMES ="names"; String FIELD_ID = "id"; 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 import import import import import import import android.content.ContentProvider; android.content.ContentUris; android.content.ContentValues; android.content.UriMatcher; android.database.Cursor; android.database.sqlite.SQLiteDatabase; android.database.sqlite.SQLiteQueryBuilder; 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);

}

} return rowsAffected;

break; default: throw new IllegalArgumentException(INVALID_URI_MESSAGE + uri);

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 import import import import import import import import android.app.ListActivity; android.content.Intent; android.database.Cursor; android.os.Bundle; android.view.Menu; android.view.MenuItem; android.view.View; android.widget.ArrayAdapter; 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 import import import import import android.app.Activity; android.database.Cursor; android.net.Uri; android.os.Bundle; android.widget.Button; 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 import import import import import import import import android.app.Activity; android.content.ContentValues; android.database.Cursor; android.net.Uri; android.os.Bundle; android.util.Log; android.view.View; android.view.View.OnClickListener; 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

Master your semester with Scribd & The New York Times

Special offer for students: Only $4.99/month.

Master your semester with Scribd & The New York Times

Cancel anytime.