ContentProvider

 

Les content provider permettent de gérer l'accès aux données et de rendre ainsi plus simple le traitement des données entre les applications.

Il s'agit en gros pour les applications d'une boîte noire qui pourra charger/stocker les données depuis ou dans un fichier, une base de données, ou même faire des accès réseaux sans pour autant impacter ces applications (y compris si le stockage de ces données venait à changer).

Image non trouvée !Il ne sera pas nécessaire de développer un Content Provider si vous n'avez pas besoin de partager ces données avec d'autres applications.

Image non trouvée !De prime abord, la création de ContentProviders perso. peut faire peur, mais en fait, la classe en elle même n'a rien de bien compliquée.

Là où cela se complique, c'est que pour fonctionner, il faudra utiliser d'autres classes, et donc il faudra éventuellement comprendre ces classes dans le même temps.

Les classes à connaitre avant de se lancer:

- Uri() et UriMatcher()

- Pour accéder aux données, généralement ce sera SQLite, et tout ce qui en découle: SQLiteOpenHelper, SQLiteDataBase, SQLiteQueryBuilder, les curseurs, et évidemment le SQL !

- J'en oublie certainement d'autres ...

Donc oui, cela peut faire peur !

Image non trouvée !L'utilisation d'un ContentProvider est beaucoup plus simple, puisqu'il s'agit d'utiliser une boîte noire. On se fiche donc de ce qui se passe derrière !

 

ContentProvider

Les content provider se composent :

- d'une Uri sous la forme:

<standard_prefix>://<autorithy>/<data_path>/<id>

Avec:

<standard prefix>: préfixe standard à "content", permettant d'indiquer qu'il s'agit d'un content provider

<autorithy>:Permet d'identifier le ContentProvider responsable de l'URI. Le nom doit être complet (par exemple fr.free.supertos.provider.nomdemonprovider)

<data_path>:Pour indiquer le type d'opération (mono/multi réponses, entité visée) sur les données que vous voulez faire.

<id>: l'identifiant de la donnée que vous voulez traiter.

-de méthodes

  • insert: insérer une nouvelle donnée,
  • update: mettre à jour une donnée,
  • delete : Effacer une donnée,
  • query : retourne un instance de cursor pour itérer sur les données,
  • getType() : Retourne le type MIME des données contenues dans le Content Provider..

Il existe plusieurs ContentProvider sous Android. Le plus classique étant celui des contactes.

 

Accéder à un ContentProvider

Pour trouver un ContentProvider répondant à notre URL, nous utiliserons la méthode getContentResolver(). Celle-ci retourne une instance ContentResolver pour notre application.

A partir de cette instance ContentResolver, il suffira d'indiquer la méthode que l'on désire lancer dans le ContentProvider en lui passant l'url demandé (avec éventuellement des paramètres pour résoudre la requète):

  • final Uri insert(Uri url, ContentValues values)

    uri: URI passé au content provider

    values: Il s'agit d'une map qui va contenir le nom des champs et la valeur de ces champs.

  • final int update(Uri uri, ContentValues values, String where, String[] selectionArgs)

    uri: URI passé au content provider

    values: Il s'agit d'une map qui va contenir le nom des champs et la valeur de ces champs.

    selection: clause where pour limiter l'effacement à certains enregistrements

    selectionArgs: Arguments qui devront être appliqués à sélection (remplacement des ? présent dans sélection par ces arguments. Premier ? par le premier argument, second ? par le second argument, ...)

    En sortie, le nombre d'enregistrements maj

  • final int delete(Uri url, String where, String[] selectionArgs)

    uri: URI passé au content provider

    selection: clause where pour limiter l'effacement à certains enregistrements

    selectionArgs: Arguments qui devra être appliqués à sélection (remplacement des ? présents dans sélection par ces arguments. Premier ? par le premier argument, second ? par le second argument, ...)

    En sortie, le nombre d'enregistrements effacés

  • final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

    uri: URI passé au content provider

    projection: listes des champs des tables accédées à retourner

    selection: clause where pour limiter l'effacement à certains enregistrements

    selectionArgs: Arguments qui devra être appliqués à sélection (remplacement des ? présent dans sélection par ces arguments. Premier ? par le premier argument, second ? par le second argument, ...)

    sortOrder ordre de trie.

  • final String getType(Uri url) pour récupérer le MIME. Cf. Traitement de l'URI dans le ContentProvider pour plus dedétails.

ContentResolver se débrouillera pour trouver le ContentProvider répondant le mieux à l'URL passée grâce à l'uri.

Exemple d'appel:

Uri uri = getContentResolver().insert(CONTENT_URI, values);

 

Image non trouvée !Ne pas confondre la récupération de données d'un ContentProvider et l'utilisation d'une activité pour afficher le résultat d'un appel à ce ContentProvider par cette activité (cf. intention implicite).

 

Image non trouvée ! Voilà, si vous n'avez pas besoin de créer votre propre ContentPorvider, vous savez tout ce qu'il y a à savoir. C'est fini ! Sinon, courage...

 

Déclaration du ContentProvider dans Androidmanifest.xml

Il faut déclarer le provider de votre application dans le fichier AndroidManifest.xml, au niveau de la balise application (pas activity !).

<provider android:name="fr.free.supertos.content.provider.NomDeMonProvider"
android:authorities="fr.free.supertos.content.provider.nomdemonprovider" />

 

Création de son propre content provider

Il faudra étendre la classe ContentProvider, puis définir les méthodes :

Création

public boolean onCreate() Lancez lors de l'instanciation de votre provider, C'est ici que vous allez pouvoir récupérer une ressource à votre source de données (par exemple en instanciant un SQLiteOpenHelper pour accéder à une base de données SQLite).

 

Insérer un enregistrement

public Cursor insert(Uri uri, ContentValues values) Pour insérer un enregistrement.

uri: URI passé au content provider

values: Il s'agit d'une map qui va contenir le nom des champs et la valeur de ces champs.

Cette méthode doit retourner une exception si l'insertion n'est pas possible :

throw new SQLException("Impossible d'insérer un enregistrement à " + uri);

Sinon, elle doit retourner l'id du nouvel enregistrement sous la forme d'URI (utilisation de Uri.parse()):

Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID); où CONTENT_URI correspond à l'URI sous la forme:

<standard_prefix>://<autorithy>/<data_path>

Il faudra ensuite notifier la réussite de l'insertion du nouvel enregistrement en faisant getContext().getContentResolver().notifyChange(_uri, null);

 

Effacer un enregistrement

public int delete(Uri uri, String selection, String[] selectionArgs) Pour supprimer un ou plusieurs enregistrements

En entrée:

uri: URI passé au content provider

selection: clause where pour limiter l'effacement à certains enregistrements

selectionArgs: Arguments qui devront être appliqués à sélection (remplacement des ? présents dans sélection par ces arguments. Premier ? par le premier argument, second ? par le second argument, ...)

 

En sortie

En cas de réussite concernant l'effacement, il faudra notifier cette réussite de la manière suivante:

getContext().getContentResolver().notifyChange(uri, null);

Puis la méthode doit retourner le nombre d'enregistrements supprimés
return count;

Si echec (par exemple sur argument, il est possible de provoquer une exception:

throw new IllegalArgumentException(“URI inconnu“ + uri);

 

Mettre à jour un ou plusieurs enregistrements

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)

En entrée:

uri: URI passé au content provider

values: Il s'agit d'une map qui va contenir le nom des champs et la valeur de ces champs.

selection: clause where pour limiter l'effacement à certains enregistrements

selectionArgs: Arguments qui devront être appliqués à sélection (remplacement des ? présents dans sélection par ces arguments. Premier ? par le premier argument, second ? par le second argument, ...)

En sortie:

En cas de réussite concernant la ou les mises à jour, il faudra notifier cette réussite de la manière suivante:

getContext().getContentResolver().notifyChange(uri, null);

Puis la méthode doit retourner le nombre d'enregistrements maj
return count;

Si echec (par exemple sur argument, il est possible de provoquer une exception:

throw new IllegalArgumentException(“URI inconnu“ + uri);

 

Requète de sélection

Cette méthode permet d'effecttuer une requète sur une entité(s) (table(s))

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

En entrée:

uri: URI passé au content provider

projection: listes des champs des tables accédées à retourner

selection: clause where pour limiter l'effacement à certains enregistrements

selectionArgs: Arguments qui devra être appliqués à sélection (remplacement des ? présents dans sélection par ces arguments. Premier ? par le premier argument, second ? par le second argument, ...)

sortOrder ordre de trie.

 

Là encore, il faudra indiquer le bon déroulement de la requète:

c étant le curseur de la query générée:

c.setNotificationUri(getContext().getContentResolver(), uri);

Cette méthode retourne le curseur sur la query.

 

Description du type de données

Il est nécessaire d'indiquer suivant l'uri reçu le type de données qui sera retourné (MIME).

public String getType(Uri uri)

Pour rappel: un mime est composé d'un type/subType

Le mime retourné devra commencer par:

vnd.android.cursor.item pour un enregistrement retourné

vnd.android.cursor.dir/ pour une multitude d'enregistrements

Généralement, vous compléterez le mime avec /vnd.nom_de_votre_application.tables_ou_entité ou un subType connu.

null s'il n'y a pas de type.

Image non trouvée !Aucune permission ne sera nécessaire aux applications pour faire cette demande.

Image non trouvée !Pour information, on trouvera une liste des MIMES officiels sur le site de l'IANA. le terme vnd vient du fait que ce type MIME n'est pas officiel, qu'il est spécifique à un éditeur. Cf. http://fr.wikipedia.org/wiki/Type_MIME

 

Traitement de l'URI

Le plus délicat sera d'interpréter l'URI afin de répondre à la requète.

Par exemple, avec une table "personnes" composée des champs id, nom et prenom, nous allons construire un provider:

autorithy: fr.free.supertos.provider.personnes

data_path: personnes pour lister toutes les personnes

personnes/id pour lister une personne dont l'id est id .

Les uri que nous pouvons recevoir seront donc:

fr.free.supertos.provider.personnes/personnes

fr.free.supertos.provider.personnes/personne/id avec id variant de 0 à n...

Il faut donc pouvoir décomposer cette URI pour les méthodes comme public String getType(Uri uri) qui doit pouvoir retourner le MIME de retour, ou pour les autres méthodes pour connaître l'id a accéder...

Pour nous y aider, nous passerons par une instance de la classe UriMatcher().

Nous n'aurons plus qu'à tester l'URI avec les différentes combinaisons déclarées dans UriMatcher qui fera tout le travail pour nous !

[...]

private static final int PERSONNES = 1;
private static final int PERSONNE_ID = 2;

private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);

static
{

sURIMatcher.addURI("fr.free.supertos.provider.personnes", "personnes", PERSONNES);
sURIMatcher.addURI("fr.free.supertos.provider.personnes", personne/#", PERSONNE_ID);

}

[...]

 

public String getType(Uri uri)
{

int match = sURIMatcher.match(uri);
switch (match)
{
case PERSONNES:
return "vnd.android.cursor.dir/vnd.supertos.personnes";
case PEOPLE_ID:
return "vnd.android.cursor.item/vnd.supertos.personnes";
default:
return null;
}

}

 

ContentProvider et SQLite

Un contentProvider sera généralement associé à une base de données SQLite. Vous constaterez que les paramètres qui seront reçus vont d'ailleurs très bien se préter à cette association !

 

D'où un exemple avec l'utilisateion de ces deux classes...

 

Exemple

L'exemple est compliqué à lire, désolé, mais je n'ai pas plus simple ! Et pourtant, il est simplifié car il n'y a qu'une table accédée et pas de la meilleur des façons !

 

Tout d'abord des constantes...pour simplifier la vie (en principe)

 

Puis la classe MySQLiteOpenHelper, là aussi pour nous simplifier la vie...(repris de l'exemple SQLite)

Création d'une base de donnée contenant une table personnes dont les champs sont id, nom et prenom.

 

Le plus lourd: PersonnesProvider qui implémente tout le nécessaire pour un provider...donc tout ce que j'ai expliqué précédemment mais détaillé !

 

Et enfin la classe testProviderActivity pour tester tout cela !

 

Le main.xml, rien de spécial dedans...

 

Et enfin AndroidManifest.xml pour déclarer notre provider.

 

Ouf ! C'était simple non ?