Content Provider: creación y uso

Podríamos decir que un Content Provider es el encargado de compartir datos entre aplicaciones, por ejemplo cuando creamos una aplicación con una base de datos SQLite esta por defecto es privada y solo accesible por la propia aplicación. Aplicándole un Content Provider podríamos hacer que esos datos estuvieran visibles para el resto de aplicaciones.

El sistema operativo Android incorpora de seria muchas aplicaciones con el uso de Content Providers como pueden ser los Contactos, Calendario, Mensajes, ...

En este articulo vamos a ver:
Ejemplo rápido de uso de un Content Provider
Creación de un Content Provider 
Uso de un Content Provider  



1. Ejemplo rápido de un Content Provider

Vamos a ver un ejemplo de lo que seria el uso de un Content Provider ya creado en una aplicación,  en el que vamos a acceder a la aplicación contactos para recuperar todos los contactos de la agenda y mostrarlos en un TextView. No me voy a parar a explicar el ejemplo ya que mas adelante explicaremos todo mas detenidamente.

Primero debemos crear un permiso en nuestro manifiesto:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
Continuamos editando nuestro layout "activity_main.xml":
<ScrollView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/contactos"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</ScrollView>

Y creamos nuestra Activity:
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        TextView texto = (TextView) findViewById(R.id.contactos);
        
        Cursor c = managedQuery(ContactsContract.Contacts.CONTENT_URI, 
        new String[] {ContactsContract.Contacts.DISPLAY_NAME}, 
        ContactsContract.Contacts.IN_VISIBLE_GROUP, 
        null,
        ContactsContract.Contacts.DISPLAY_NAME);

        while (c.moveToNext()) {
            String contactos = c.getString(c
                .getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
            texto.append(contactos);
            texto.append("\n");
        }
    }
}
Si ejecutamos este código, veremos como nos muestra en la pantalla del móvil/emulador una lista de nuestros contactos de la agenda.




2. Creación de un Content Provider

Para la creación de un Content Provider en nuestra aplicación debemos tener en cuenta varios factores:
  • Que tipo de archivos vamos a compartir con el resto de aplicaciones. Tenemos varias opciones: compartir archivos normales, bases de datos SQLite o incluso archivos de red.
  • Deberemos crear una clase que extienda de Content Provider con los métodos necesarios y esta sera la encargada de compartir los datos con el resto de aplicaciones. 
  • Definir las URIs que nos sean necesarias, simplemente son enlaces simbólicos hacia los datos que queremos compartir, similar a los enlaces web.
Primero vamos a ver como crear una URI, su estructura seria la siguiente:
<prefijo>://<autoridad>/<datos>/<id>
Como prefijo en este caso se usa "content" ya que estamos tratando con un Content Provider. En la autoridad especificaremos el identificador del Content Provider similar al nombre de un paquete de una aplicación, en este caso usaremos "mi.content.provider.contactos". En los datos se indica que datos se quieren compartir, en el ejemplo vamos a compartir una tabla de SQLite llamada "contactos". Y la id es opcional, indicaríamos el registro a compartir. Por lo tanto nuestra URI y la que usaremos en el ejemplo quedará de la siguiente manera:
content://mi.content.provider.contactos/contactos 
Tras ver varios ejemplo he decidido hacer un ejemplo muy similar al uso de base de datos para que se entienda rápidamente lo que seria un Content Provider.


2.1 Crear base de datos SQLite

Empezaríamos creando una base de datos SQLite con una única tabla, para ello os remito al articulo: Base de datos SQLite en caso de que no sepamos como hacerlo. Necesitamos crear una tabla contactos y en este caso el campo "_id" es obligatorio ya que los Content Provider necesitan de un valor único para identificar los registros.


2.2 Creación de la clase Content Provider

Para crear un content provider primero deberemos crear una nueva clase en nuestro proyecto y hacer que extienda de "ContentProvider", al hacer esto nos pide sobreescribir varios métodos:
onCreate: llamado al iniciar el Content Provider
getType: devuelve el tipo MIME de los datos del Content Provider (MIME se podría definir como una serie de convenciones o especificaciones dirigidas al intercambio de todo tipo de archivos (texto, audio, vídeo, ...) a través de Internet de una forma transparente para el usuario) 
insert: inserta nuevos datos en el Content Provider
update: actualiza datos existentes en el Content Provider
delete: borra datos del Content Provider
query: devuelve los datos a la persona que los consulte 

2.3 Variables de la clase

Vamos a empezar explicando las variable que vamos a necesitar en nuestra clase "MiContentProvider". Seguidamente iremos explicando los método a sobreescribir de esta clase.
    private MiBaseDatos MBD;
    SQLiteDatabase SQLDB;
    private static final String NOMBRE_CP = "mi.content.provider.contactos";
 
    private static final int CONTACTOS = 1;
    private static final int CONTACTOS_ID = 2;
    private static final UriMatcher uriMatcher = 
            new UriMatcher(UriMatcher.NO_MATCH);

    static{
        uriMatcher.addURI(NOMBRE_CP, "contactos", CONTACTOS);
        uriMatcher.addURI(NOMBRE_CP, "contactos/#", CONTACTOS_ID);
    });
Primeramente creamos una instancia a nuestra base de datos y un objeto "SQLiteDataBase" que sera el encargado de hacer las consultas CRUD (crear, leer, actualizar, borrar) en nuestra base de datos.

Continuamos creando el identificador de nuestro Content Provider "NOMBRE_CP".

Y terminamos creando un objeto "uriMatcher" que sera el encargado de almacenar las direcciones Uri que necesitemos en nuestro proyecto y por lo tanto facilitarnos mas el trabajo. Para ello hacemos uso del método "addURI(authority, path, code)" que nos pide como parametro la autoridad, como segundo parámetro la ruta de los datos y como tercer parámetro un identificador que debe ser siempre positivo.

Comentar que la clase "UriMatcher" suele usarse con el método java "switch" y de ahí el tercer parámetro "code". Por lo tanto podríamos crear tantas Uris como nos fuera necesario. En este caso hemos creado dos, una para consultar todos los contactos de la tabla y otra para consultar un solo registro, para ello usamos # que indica que estamos buscando números.


2.4 Sobreescribir métodos

Empezamos a definir los métodos de la clase Content Provider. En este ejemplo no vamos a usar el método "getType()" por lo tanto lo dejamos como esta y pasamos al "onCreate()":
    public boolean onCreate() {
        MBD = new MiBaseDatos(getContext());
        return true;
    }
Aquí simplemente iniciamos nuestra base de datos.


2.4.1 Insertar registros
public Uri insert(Uri uri, ContentValues values) {
    long registro = 0;
    try {
        if (uriMatcher.match(uri) == CONTACTOS) {
            SQLDB = MBD.getWritableDatabase();
            registro = SQLDB.insert("contactos", null, values);
        }
    } catch (IllegalArgumentException e) {
        Log.e("ERROR", "Argumento no admitido: " + e.toString());
    }
  
    // Comprobar si se inserto bien el registro
    if (registro > 0) {
        Log.e("INSERT", "Registro creado correctamente");
    } else {
        Log.e("Error", "Al insertar registro: " + registro);
    }
  
    return Uri.parse("contactos/" + registro);
}
La variable "registro" la usaremos para comprobar si se inserto bien el registro, gracias al método insert que nos devuelve la id del registro insertado en caso de que lo haya hecho bien.

Encapsulamos todo en un bloque try-catch para tratar la excepción "IllegalArgumentExcepcion" que se produce cuando se llama a un método con un argumento que no es razonable de tratar. Dentro comprobamos el argumento "uri" y si es igual a nuestra uriMatcher "CONTACTOS" ponemos nuestra base de datos en modo escritura e insertamos el registro en nuestra tabla "contactos" usando el argumento values.

El método insert devuelve la uri del nuevo registro.


2.4.2 Actualizar registros
public int update(Uri uri, ContentValues values, String selection,
        String[] selectionArgs) {
  
    String id = "";
    try {
        if (uriMatcher.match(uri) == CONTACTOS_ID) {
            id = uri.getLastPathSegment();
            SQLDB = MBD.getWritableDatabase();
            SQLDB.update("contactos", values, "_id=" + id, selectionArgs);
        }
    } catch (IllegalArgumentException e) {
        Log.e("ERROR", "Argumento no admitido: " + e.toString());
    }
  
    return Integer.parseInt(id);
}
Método muy similar al anterior, pero en este caso comprobamos que la uri sea igual a nuestra usriMatcher "CONTACTOS_ID". En caso de que sea así almacenamos el registro a actualizar en la variable "id" y para ello usamos el método "getLastPathSegment()" que nos devuelve el ultimo segmento de la uri decodificado.

Actualizamos el registro según su id usando el argumento values. No me paro a explicar mucho estos métodos ya que lo tenemos muy bien explicado en el articulo: Bases de datos SQLite.

Este método nos devolverá la id que hemos actualizado.


2.4.3 Borrar registros
public int delete(Uri uri, String selection, String[] selectionArgs) {
    int registro = 0;
    try {
        if (uriMatcher.match(uri) == CONTACTOS_ID) {
            String id = "_id=" + uri.getLastPathSegment();
            SQLDB = MBD.getWritableDatabase();
            registro = SQLDB.delete("contactos", id, null);
        }
    } catch (IllegalArgumentException e) {
        Log.e("ERROR", "Argumento no admitido: " + e.toString());
    }

    return registro;
}
Aquí mas de lo mismo, comprobamos la uri y si es igual almacenamos la id del registro y lo borramos con el método delete.

Este método nos devolverá la id del registro borrado.


2.4.4 Consultar registros
public Cursor query(Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {
    
    Cursor c = null;
    try {
        switch (uriMatcher.match(uri)) {
        case CONTACTOS_ID:
            String id = "_id=" + uri.getLastPathSegment();
            SQLDB = MBD.getReadableDatabase();
            c = SQLDB.query("contactos", projection, id, selectionArgs, 
                    null, null, null, sortOrder);
            break;
        case CONTACTOS:
            SQLDB = MBD.getReadableDatabase();
            c = SQLDB.query("contactos", projection,   null, selectionArgs, 
                    null, null, null, sortOrder);
            break;
        }
    } catch (IllegalArgumentException e) {
        Log.e("ERROR", "Argumento no admitido: " + e.toString());
    }

    return c;
}
En este método vamos a usar nuestras dos uriMatcher, una para el caso de consultar un registro y la otra para el caso de querer consultar todos los registros.

En este caso ponemos nuestra base de datos en modo lectura y cargamos las consultas en un cursor ya que este método devuelve un cursor.


2.5 Registrar el Content Provider en el manifiesto

Para terminar nuestra proyecto deberemos incluir estas lineas en el AndroidManifest.xml, dentro de application y justo debajo de la activity:
    <provider
        android:name=".MiContentProvider"
        android:authorities="mi.content.provider.contactos" >
    </provider>
Aquí le indicamos que la clase "MiContentProvider" va a ser un Content Provider y la autoridad para acceder a el.




3. Uso de un Content Provider

He decidido incluir la Activity que manejara nuestro content provider en este ejemplo para no tener que instalar dos aplicaciones. Pero si creáramos otro proyecto con esta Activity y teniendo en cuenta que tendríamos que tener instalada la aplicación del content provider, veríamos el uso de nuestro content provider en otras aplicaciones.

A continuacion dejo la Activity que vamos a usar en el ejemplo:
public class MainActivity extends Activity {
 
    private static final Uri URI_CP = Uri.parse(
            "content://mi.content.provider.contactos/contactos");
    
    private Uri uri;
    private Cursor c;
 
    private int id;
    private String nombre;
    private int telefono;
    private String email;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ContentResolver CR = getContentResolver();
        
        // Insertamos 4 registros
        CR.insert(URI_CP, setVALORES(1, "Pedro", 111111111, "pedro@DB.es"));
        CR.insert(URI_CP, setVALORES(2, "Sandra", 222222222, "sandra@DB.es"));
        uri = CR.insert(URI_CP, setVALORES(3, "Mar", 333333333, "mar@DB.es"));
        Log.d("REGISTRO INSERTADO", uri.toString());
        uri = CR.insert(URI_CP, setVALORES(4, "Dani", 444444444, "dani@DB.es"));
        Log.d("REGISTRO INSERTADO", uri.toString());
        
        
        // Recuperamos todos los registros de la tabla
        String[] valores_recuperar = {"_id", "nombre", "telefono", "email"};
        c = CR.query(URI_CP, valores_recuperar, null, null, null);
        c.moveToFirst();
        do {
            id = c.getInt(0);
            nombre = c.getString(1);
            telefono = c.getInt(2);
            email = c.getString(3);
            Log.d("QUERY", "" +id+ ", " +nombre+ ", " +telefono+ ", " +email);
        } while (c.moveToNext());
        
        
        // Actualizamos un registro de la tabla
        uri = Uri.parse("content://mi.content.provider.contactos/contactos/3");
        CR.update(uri, setVALORES(3, "PPPPP", 121212121, "xxxx@xxxx.es"), 
                null, null);
        // Y lo mostramos en el log
        c = CR.query(uri, valores_recuperar, null, null, null);
        c.moveToFirst();
        id = c.getInt(0);
        nombre = c.getString(1);
        telefono = c.getInt(2);
        email = c.getString(3);
        Log.d("QUERY", "" +id+ ", " +nombre+ ", " +telefono+ ", " +email);

        
        // Borramos un registro
        uri = Uri.parse("content://mi.content.provider.contactos/contactos/4");
        CR.delete(uri, null, null);
        
    }

    private ContentValues setVALORES(int id, String nom, int tlf, String email) {
        ContentValues valores = new ContentValues();
        valores.put("_id", id);
        valores.put("nombre", nom);
        valores.put("telefono", tlf);
        valores.put("email", email);
        return valores;
    }
}
Las variables que estamos usando son "URI_CP" que sera la dirección donde esta ubicado el content provider, "uri" la usaremos para manejar las consultar a través de su id, "c" el cursor donde almacenaremos los datos y 4 variables donde almacenaremos cada valor de un registro.

Ya en el onCreate creamos un objeto "ContentResolver" que sera el encargado de acceder a un content provider y con el método "getContentResolver" conseguimos la instancia de un Content Resolver.

Para insertar registros usamos el método "insert(url, values)" que nos pide como parámetro la dirección del content resolver "url" y un ContentValues los valores a insertar"values". Para insertar los valores hemos creado un método auxiliar "setVALORES(id, nom, tlf, email)" que nos facilitara las cosas ya que poniendo como parámetro los datos a insertar, este nos devuelve un ContentValues con los datos del registro. El método "insert" nos devuelve la id del registro a insertar, he puesto dos ejemplos para que veamos su uso en el log.

Para consultar todos los registros hacemos uso del método "query(url, projection, selection, selectionArgs, sortOrder)" pasándole la dirección uri del Content Provider. En el articulo base de datos se explica mas detalladamente esta función de consulta.

Actualizamos un registro con el metodo "update(url, values, where, selectionArgs)" pasándole una uri personalizada con el registro a actualizar, en este caso el numero 3.

Y para concluir el articulo borramos un registro con el método "delete(url, where, selectionArgs)" pasandole una uri con la id del registro que vamos a borrar.



CODIGO DE EJEMPLO: CONTENT PROVIDER

No hay comentarios:

Publicar un comentario