La rubrica di Android è la sorgente principale di tutti i recapiti telefonici da poter contattare. Considerando che si tratta di un
insieme di dati strutturati, disponibili a livello di sistema, non stupisce il fatto che essa sia stata implementata nei termini di un ContentProvider.
I contatti, ai quali normalmente ci si riferisce con il termine rubrica, rappresentano uno dei principali ContentProvider
disponibili su Android. La sua struttura può sembrare piuttosto confusa, e per questo occorre innanzitutto fare un po' d’ordine.
Sul funzionamento dei ContentProvider non c'è nulla di nuovo da aggiungere rispetto a quanto visto nelle precedenti lezioni. In questa sede
ricordiamo brevemente che un ContentProvider permette di accedere a dati condivisi tramite le classiche operazioni di
creazione-lettura-modifica-cancellazione mediante i metodi insert, query, update e delete. A differenza di quanto
accade con i database relazionali, essi non richiedono di accedere direttamente a tabelle, bensì di individuare la risorsa oggetto dell'operazione mediante
un riferimento univoco di classe Uri.
Proprio dall'organizzazione delle classi e dal reperimento degli URI inizia questo capitolo. L'organizzazione dei contatti viene
distribuita su tre livelli:
- Contact;
- RawContact;
- Data.
Prima di spiegare le differenze tra i tre, si tenga a mente che per utilizzare un dispositivo Android, l’utente deve associare ad esso uno o più account Google. Il sistema operativo deve quindi prendersi carico di registrare i dati dei contatti ed accoppiarli con l’account
che li utilizza.
Mentre i Contact rappresentano singole persone indipendentemente dalla quantità di informazioni che di ognuna di esse si posseggono (email,
telefono di casa, telefono dell’ufficio, cellulare, etc.), i RawContact includono l’associazione tra un contatto e un account. Inoltre per ogni
persona esiste un solo Contact e per ogni Contact possono esistere più RawContact.
I Data invece rappresentano i singoli dettagli che formano ogni RawContact.
Le classi per la gestione dei contatti che useremo saranno pertanto tre:
- ContactsContract.Contacts, i singoli contatti;
- ContactsContract.RawContacts, i “raw contact”;
- ContactsContract.Data, i dati di dettaglio.
Le permission
Consueto obbligo è quello di dichiarare le permission adeguate, dipendentemente dalle operazioni che si vogliono svolgere:
- per la sola lettura dei contatti, utilizzeremo la seguente sintassi:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
- se vogliamo anche inserire e modificare i dati, includeremo quanto segue:
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
Lettura dei contatti
L'Uri per l'accesso all'insieme di dati (Contact, RawContact o Data) è contenuto in una costante stringa di nome CONTENT_URI, della classe ContactsContract.Contacts.
L'elenco dei contatti disponibili può essere letto tramite un Cursor con le seguenti righe di codice:
String[] projection={Contacts._ID,Contacts.DISPLAY_NAME};
Cursor crs=getContentResolver()
.query(ContactsContract.Contacts.CONTENT_URI, projection, null, null, null);
In questo esempio, i dati inseriti nella proiezione sono solo due dei tanti disponibili, ma sono quelli più utili: l'ID del contatto, indispensabile per
accedere a tutti gli altri dati, ed il nome del contatto.
Possiamo visualizzarli, come fatto in altre occasioni, combinando l’utilizzo delle classi CursorAdapter e ListView.
Inserimento di dati
Immaginiamo di voler inserire alcuni dati per un nuovo RawContact. Non dovremo fare altro che creare il nuovo contatto, ottenerne l’ID ed
utilizzarlo per inserire singoli Data:
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
operations.add(ContentProviderOperation
.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build());
operations.add(ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, “Guido Rossi”)
.build());
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, “0611223344”)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_HOME)
.build());
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.DATA, “guido@rossi.it”)
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.TYPE_WORK)
.build());
try {
cr.applyBatch(ContactsContract.AUTHORITY, operations);
}
catch (RemoteException e)
{
// gestione delle eccezioni
}
catch (OperationApplicationException e)
{
// gestione delle eccezioni
}
Quando operazioni di questo tipo vengono effettuate come un'aggregazione di diversi inserimenti, può essere utile usare le ContentProviderOperation. I loro principali fattori di utilità sono l'uso di un builder per comporre in modo agevole la richiesta, e
l'esecuzione dell’operazione in batch.
Nelle righe di codice precedenti per prima cosa viene creato un ArrayList di ContentProviderOperation. Poi vi si aggiungono una alla
volta una serie di operazioni, utilizzando questi comandi:
- newInsert
, per creare il nuovo inserimento; - withValue
, per specificare i parametri da inserire; - build
, per costruire il comando completo.
L'esecuzione vera e propria avverrà richiedendo al ContentResolver di eseguire il batch con il metodo applyBatch.