En el artículo anterior del Curso de Programación en Android vimos como construir un content provider personalizado para permitir a nuestras aplicaciones Android compartir datos con otras aplicaciones del sistema. En este nuevo artículo vamos a ver el tema desde el punto de vista opuesto, es decir, vamos a aprender a hacer uso de un content provider ya existente para acceder a datos de otras aplicaciones. Además, veremos cómo también podemos acceder a datos del propio sistema Android (logs de llamadas, lista de contactos, agenda telefónica, bandeja de entrada de sms, etc) utilizando este mismo mecanismo.
Vamos a comenzar explicando cómo podemos utilizar el content provider que implementamos en el artículo anterior para acceder a los datos de los clientes. Para no complicar mucho el ejemplo ni hacer más dificil las pruebas y la depuración en el emulador de Android vamos a hacer uso el content provider desde la propia aplicación de ejemplo que hemos creado. De cualquier forma, el código necesario sería exactamente igual si lo hiciéramos desde otra aplicación distinta.
Utilizar un content provider ya existente es muy sencillo, sobre todo comparado con el laborioso proceso de construcción de uno nuevo. Para comenzar, debemos obtener una referencia a un Content Resolver, objeto a través del que realizaremos todas las acciones necesarias sobre el content provider. Esto es tan fácil como utilizar el método getContentResolver() desde nuestra actividad para obtener la referencia indicada. Una vez obtenida la referencia al content resolver, podremos utilizar sus métodos query(), update(), insert() y delete() para realizar las acciones equivalentes sobre el content provider. Por ver varios ejemplos de la utilización de estos métodos añadiremos a nuestra aplicación de ejemplo tres botones en la pantalla principal, uno para hacer una consulta de todos los clientes, otro para insertar registros nuevos, y el último para eliminar todos los registros nuevos insertados con el segundo botón.
Empecemos por la consulta de clientes. El procedimiento será prácticamente igual al que vimosen los artículos de acceso a bases de datos SQLite (consultar el índice del curso). Comenzaremos por definir un array con los nombres de las columnas de la tabla que queremos recuperar en los resultados de la consulta, que en nuestro caso serán el ID, el nombre, el teléfono y el email. Tras esto, obtendremos como dijimos antes una referencia al content resolver y utilizaremos su método query() para obtener los resultados en forma de cursor. El método query() recibe, como ya vimos en el artículo anterior, la Uri del content provider al que queremos acceder, el array de columnas que queremos recuperar, el criterio de selección, los argumentos variables, y el criterio de ordenación de los resultados. En nuestro caso, para no complicarnos utilizaremos tan sólo los dos primeros, pasándole el CONTENT_URI de nuestro provider y el array de columnas que acabamos de definir.
//Columnas de la tabla a recuperar String[] projection = new String[] { Clientes._ID, Clientes.COL_NOMBRE, Clientes.COL_TELEFONO, Clientes.COL_EMAIL }; Uri clientesUri = ClientesProvider.CONTENT_URI; ContentResolver cr = getContentResolver(); //Hacemos la consulta Cursor cur = cr.query(clientesUri, projection, //Columnas a devolver null, //Condición de la query null, //Argumentos variables de la query null); //Orden de los resultados
Hecho esto, tendremos que recorrer el cursor para procesar los resultados. Para nuestro ejemplo, simplemente los escribiremos en un cuadro de texto (txtResultados) colocado bajo los tres botones de ejemplo. Una vez más, si tienes dudas sobre cómo recorrer un cursor, puedes consultar los artículos del curso dedicados al tratamiento de bases de datos SQLite, por ejemplo éste. Veamos cómo quedaría el código:
if (cur.moveToFirst()) { String nombre; String telefono; String email; int colNombre = cur.getColumnIndex(Clientes.COL_NOMBRE); int colTelefono = cur.getColumnIndex(Clientes.COL_TELEFONO); int colEmail = cur.getColumnIndex(Clientes.COL_EMAIL); txtResultados.setText(""); do { nombre = cur.getString(colNombre); telefono = cur.getString(colTelefono); email = cur.getString(colEmail); txtResultados.append(nombre + " - " + telefono + " - " + email + "\n"); } while (cur.moveToNext()); }
Para insertar nuevos registros, el trabajo será también exactamente igual al que se hace al tratar directamente con bases de datos SQLite. Rellenaremos en primer lugar un objeto ContentValues con los datos del nuevo cliente y posteriormente utilizamos el método insert() pasándole la URI del content provider en primer lugar, y los datos del nuevo registro como segundo parámetro.
ContentValues values = new ContentValues(); values.put(Clientes.COL_NOMBRE, "ClienteN"); values.put(Clientes.COL_TELEFONO, "999111222"); values.put(Clientes.COL_EMAIL, "nuevo@email.com"); ContentResolver cr = getContentResolver(); cr.insert(ClientesProvider.CONTENT_URI, values);
Por último, y más sencillo todavía, la eliminación de registros la haremos directamente utilizando el método delete() del content resolver, indicando como segundo parámetro el criterio de localización de los registros que queremos eliminar, que en este caso serán los que hayamos insertado nuevos con el segundo botón de ejemplo (aquellos con nombre = ‘ClienteN’).
ContentResolver cr = getContentResolver(); cr.delete(ClientesProvider.CONTENT_URI, Clientes.COL_NOMBRE + " = 'ClienteN'", null);
Como muestra gráfica, veamos por ejemplo el resultado de la consulta de clientes (primer botón) en la aplicación de ejemplo.
Con esto, hemos visto lo sencillo que resulta acceder a los datos proporcionados por un content provider. Pues bien, éste es el mismo mecanismo que podemos utilizar para acceder a muchos datos de la propia plataforma Android. En la documentación oficial del paquete android.provider podemos consultar los datos que tenemos disponibles a través de este mecanismo, entre ellos encontramos por ejemplo: el historial de llamadas, la agenda de contactos y teléfonos, las bibliotecas multimedia (audio y video), o el historial y la lista de favoritos del navegador.
Por ver un ejemplo de acceso a este tipo de datos, vamos a realizar una consulta al historial de llamadas del dispositivo, para lo que accederemos al content provider android.provider.CallLog.
En primer lugar vamos a registrar varias llamadas en el emulador de Android, de forma que los resultados de la consulta al historial de llamadas contenga algunos registros. Haremos por ejemplo varias llamadas salientes desde el emulador y simularemos varias llamadas entrantes desde el DDMS. Las primeras son sencillas, simplemente ve al emulador, accede al teléfono,marca y descuelga igual que lo harías en un dispositivo físico. Y para emular llamadas entrantes podremos hacerlo una vez más desde Eclipse, accediendo a la vista del DDMS. En esta vista, si accedemos a la sección «Emulator Control» veremos un apartado llamado «Telephony Actions«. Desde éste, podemos introducir un número de teléfono origen cualquiera y pulsar el botón «Call» para conseguir que nuestro emulador reciba una llamada entrante. Sin aceptar la llamada en elemulador pulsaremos «Hang Up» para teminar la llamada simulando así una llamada perdida.
Hecho esto, procedemos a realizar la consulta al historial de llamadas utilizando el content provider indicado, y para ello añadiremos un botón más a la aplicación de ejemplo.
Consultando la documentación del content provider veremos que podemos extraer diferentes datos relacionados con la lista de llamadas. Nosotros nos quedaremos sólo con dos significativos, el número origen o destino de la llamada, y el tipo de llamada (entrante, saliente, perdida). Los nombres de estas columnas se almacenan en las constantes Calls.NUMBER y Calls.TYPE respectivamente.
Decidido esto, actuaremos igual que antes. Definiremos el array con las columnas que queremos recuperar, obtendremos la referencia al content resolver y ejecutaremos la consulta llamando al método query(). Por último, recorremos el cursor obtenido y procesamos los resultados. Igual que antes, lo único que haremos será escribir los resultados al cuadro de texto situado bajo los botones. Veamos el código:
String[] projection = new String[] { Calls.TYPE, Calls.NUMBER }; Uri llamadasUri = Calls.CONTENT_URI; ContentResolver cr = getContentResolver(); Cursor cur = cr.query(llamadasUri, projection, //Columnas a devolver null, //Condición de la query null, //Argumentos variables de la query null); //Orden de los resultados if (cur.moveToFirst()) { int tipo; String tipoLlamada = ""; String telefono; int colTipo = cur.getColumnIndex(Calls.TYPE); int colTelefono = cur.getColumnIndex(Calls.NUMBER); txtResultados.setText(""); do { tipo = cur.getInt(colTipo); telefono = cur.getString(colTelefono); if(tipo == Calls.INCOMING_TYPE) tipoLlamada = "ENTRADA"; else if(tipo == Calls.OUTGOING_TYPE) tipoLlamada = "SALIDA"; else if(tipo == Calls.MISSED_TYPE) tipoLlamada = "PERDIDA"; txtResultados.append(tipoLlamada + " - " + telefono + "\n"); } while (cur.moveToNext()); }
Lo único fuera de lo normal que hacemos en el código anterior es la decodificación del valor del tipo de llamada recuperado, que la hacemos comparando el resultado con las constantes Calls.INCOMING_TYPE (entrante), Calls.OUTGOING_TYPE (saliente), Calls.MISSED_TYPE (perdida) proporcionadas por la propia clase provider.
Un último detalle importante. Para que nuestra aplicación pueda acceder al historial de llamadas del dispositivo tendremos que incluir en el fichero AndroidManifest.xml el permiso READ_CONTACTS y READ_CALL_LOG utilizando la cláusula <uses-permission> correspondiente.
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission> <uses-permission android:name="android.permission.READ_CALL_LOG"></uses-permission>
Si ejecutamos la aplicación y realizamos la consulta podremos ver un resultado similar al siguiente:
Y con esto terminamos con el tema dedicado a los content providers. Espero que os haya sido útil para aprender a incluir esta funcionalidad a vuestras aplicaciones y a utilizar este mecanismo para acceder a datos propios del sistema.
Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la página del curso en GitHub.
Curso de Programación Android en PDF
¿Te ha sido de utilidad el Curso de Programación Android? ¿Quieres colaborar de forma económica con el proyecto? Puedes contribuir con cualquier cantidad, unos céntimos, unos euros, cualquier aportación será bienvenida. Además, si tu aportación es superior a una pequeña cantidad simbólica recibirás como agradecimiento un documento con la última versión del curso disponible en formato PDF. Sea como sea, muchas gracias por colaborar!
Más información:
17 comentarios
[…] Content Providers en Android (II): Utilización [Nuevo!] […]
Gracias,como siempre un buen tutorial.
Muchas gracias!!
Muy buen articulo, me vendrá muy bien para un trabajo que estoy desarrollando.
Hey man muchas gracias, y para cuando el segundo articulo?
tengo una pequeña duda, no existe ningún Conten Provider para acceder a los sms enviados o recibidos igual al que se muestra en este ejemplo para las llamadas???
Excelente artículo. Lo que creo se que no llegaste a mostrar como acceder a un solo registro, es decir, en que momento le pasas al content resolver el ID que quieres obtener
sgoliver, veo que usas el getContentResolver(). Según investigué lo más recomendable hoy en día es usar los Loaders pero en el caso de usar el getContentResolver() donde te parece el mejor lugar para cerrar el cursor? en el @Destroy?
Lo otro que se me ocurre es que usando el AsyncTask podría cerrar el cursor en el método onPostExecute(). Te parecería correcto esto?
En primer lugar felicitarte por el buen trabajo que has hecho, muy bueno.
Queria comentarte que para acceder al log de llamadas me ha solicitado el siguiente permiso
no el de contactos.
Saludos
el permiso referido es READ_CALL_LOG, que antes lo he pegado y creo que no ha salido una vez lo he enviado. saludos!!
Muchas gracias por el tuto! eres un máquina! empecé leyendo uno pero creo que voy a seguir el curso entero. No se si tendrás pensado para el futuro hacerlo pero vendría genial debido a su complicación un tutorial de SyncAdapter. Gracias!
Gracias por tus buenos tutoriales.
Hola,
No tengo consultas, solo queria agradecerte tu gran trabajo, MUCHAS GRACIAS:
[…] Content Providers en Android (II): Utilización [v3] […]
Consulta si tengo dos tablas por ejemplo
//URI DE CONTENIDO PRINCIPAL
public final static Uri CONTENT_URI = Uri.parse(«content://» + AUTHORITY + «/» + tablaProductos);
public final static Uri CONTENT_URI2 = Uri.parse(«content://» + AUTHORITY + «/» + tablaCategoria);
public static class ColumnasProductos implements BaseColumns{
private Columnas(){ }
public static final String NOMBREPRODUCTO= «nombreproducto»;
public static final String ID_CATEGORIA = «id_categoria»;
}
public static class ColumnasCategoria implements BaseColumns{
private Columnas(){ }
public static final String NOMBRECATEGORIA= «nombrecategoria»;
}
como hago para consultar la tablaProductos y traer el nombrecategoria ?? si tengo dos URI , como se hacen las consultas con ContentResolver.query(…) en ese caso
Muchas Gracias.
No lo entiendo. Al final nisiquiera usas la clase ClientesProvider que creaste antes. Solo utilizas las constantes de esa clase, como ClientesProvider.URI. Pero no creas una instancia de esa clase ni nada por el estilo. ¿Para que sirve entonces esa clase?
Gracias buen tutorial!