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).
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.
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 !
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);
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).
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.
Aucune permission ne sera nécessaire aux applications pour faire cette demande.
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 ?