Uno dei principi che è più volte riemerso nel corso di questa guida è la riservatezza dei dati dell’applicazione. Lo si è anticipato già nei primi capitoli e lo si è sperimentato studiando i metodi di persistenza: i file ed i database creati devono essere utilizzati solo dall’applicazione cui appartengono. In effetti, l’esistenza dello stesso Internal Storage ne è già una conferma.
Ma allora – ci si potrebbe chiedere – come possono le applicazioni condividere i propri dati con il resto del sistema?
Un meccanismo di condivisione esiste ed è rappresentato dai ContentProvider, una delle quattro componenti delle app Android, oltre ad Activity, Service e BroadcastReceiver.
Questo capitolo si occuperà di mostrare le caratteristiche di base di un ContentProvider e di come sia possibile crearne uno nella propria applicazione. L’esempio che ci guiderà in questo percorso sarà la “trasformazione” in ContentProvider del database a supporto dello scadenziario visto nel capitolo precedente.
I ContentProvider sono riconoscibili mediante un URI, un riferimento univoco. Questi indirizzi sono costituiti da due parti:
Un ContentProvider, come avviene per le Activity, deve essere definito in due step:
Appena definita la classe ContentProvider, viene richiesto di implementarne i metodi astratti, tra cui query, update, delete e insert. Definiscono le operazioni CRUD ed i parametri richiesti ricordano in tutto – tipologia e funzioni – quelli usati per i database come illustrato nel capitolo precedente. La differenza sarà la mancanza del nome della tabella, sostituita dall’indicazione dell’URI.
Svolti questi passi, qualunque applicazione nel sistema sarà in grado di accedere alla sorgente dati sottesa al ContentProvider.
Sarà sufficiente fare accesso ad un oggetto di sistema, detto ContentResolver e chiedergli di svolgere operazioni sugli URI conosciuti del ContentProvider. Ogni operazione CRUD che verrà chiesta al ContentResolver sarà convertita in una chiamata al corrispondente metodo CRUD della classe ContentProvider.
Il ContentResolver sarà in grado di stabilire la relazione tra URI e classe Java grazie ai dati registrati nel manifest dell’applicazione.
Per attuare l’adattamento del database a ContentProvider, iniziamo con la definizione degli URI. Scegliamo il package Java (“it.html.guida.database”) come authority e che tutti i path inizino con il segmento “/scadenze”. Queste sono decisioni che spettano al programmatore.
Le azioni che potranno essere invocate sul ContentProvider sono le stesse del precedente capitolo:
e questi gli URI presso i quali potranno, rispettivamente, essere richieste:
Il ContentProvider verrà implementato dalla classe BillBookProvider mentre la MainActivity rimarrà praticamente invariata. Non sarà più necessario usare oggetti DbManager per accedere ai dati. Il collegamento alla sorgente dati avverrà, in maniera “remota”, attraverso URI e ContentResolver.
All’atto pratico questo determinerà, innanzitutto, che il collegamento al helper sarà contenuto nella classe ContentProvider ed è qui che queste componenti dimostrano un aspetto molto importante in termini architetturali: il disaccoppiamento totale tra lo strato di presentazione (l’Activity) e il livello di accesso ai dati.
La registrazione del ContentProvider nell’AndroidManifest.xml avverrà così:
<application . . . . . .> <activity . . . . . ./> <provider android:name=".BillBookProvider" android:authorities="it.html.guida.database"/> </application>
associando authority e classe Java.
Ecco il ContentProvider:
public class BillBookProvider extends ContentProvider { private DBhelper dbhelper=null; @Override public boolean onCreate() { dbhelper=new DBhelper(getContext()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) { Cursor crs=null; try { SQLiteDatabase db=dbhelper.getReadableDatabase(); crs=db.query(DatabaseStrings.TBL_NAME, null, null, null, null, null, null, null); } catch(SQLiteException sqle) { return null; } return crs; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { SQLiteDatabase db=dbhelper.getWritableDatabase(); int res=-1; String id=uri.getLastPathSegment(); try { res=db.delete(DatabaseStrings.TBL_NAME, DatabaseStrings.FIELD_ID+"=?", new String[]{id}); } catch (SQLiteException sqle) { // Gestione delle eccezioni } return res; } @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase db=dbhelper.getWritableDatabase(); long id=-1; try { db.insert(DatabaseStrings.TBL_NAME, null,values); } catch (SQLiteException sqle) { return null; } return Uri.withAppendedPath(uri, Long.toString(id)); } @Override public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) { // modifica non implementata return 0; } @Override public String getType(Uri uri) { return null; } }
Non ripetiamo il codice dell’Activity perchè ha subito, come detto, modifiche minime. È interessante però notare come cambia l’accesso alla persistenza.
Senza ContentProvider, avremmo chiesto l’elenco delle scadenze così:
db=new DbManager(this); Cursor crs=db.query();
ora, non abbiamo più bisogno del DbManager, pertanto faremo questo:
Cursor crs=getContentResolver().query(Uri.parse("content://it.html.guida.database/scadenziario"), null, null, null, null);
Il riferimento al ContentResolver viene fornito dal Context. Analogamente potremo richiedere l’inserimento di una nuova scadenza all’interno del metodo salva:
ContentValues cv=new ContentValues(); cv.put(DatabaseStrings.FIELD_SUBJECT, sub.getEditableText().toString()); cv.put(DatabaseStrings.FIELD_TEXT, txt.getEditableText().toString()); cv.put(DatabaseStrings.FIELD_DATE, date.getEditableText().toString()); getContentResolver().insert(Uri.parse("content:// it.html.guida.database /scadenze/nuova"), cv);
o la cancellazione mediante id nell’implementazione di OnClickListener:
long id=adapter.getItemId(position); // id dell'elemento getContentResolver().delete(Uri.withAppendedPath(Uri.parse("content://it.html.guida.database /scadenze/elimina"), Long.toString(id)),null,null);
Ultima nota, lavorando con gli URI, è utile guardare con attenzione la documentazione per scoprire i vari metodi di utilità che si possono usare. Ne sono esempio parse e withAppendedPath qui utilizzati.
inserisci la tua e-mail nel box qui sotto: