Condividere i dati con i Content Provider

16 luglio 2014

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.

Funzionamento di base di un ContentProvider

I ContentProvider sono riconoscibili mediante un URI, un riferimento univoco. Questi indirizzi sono costituiti da due parti:

  • authority: è il nome del Provider in generale. Spesso, per evitare conflitti, richiama il nome del package Java di appartenenza;
  • path: costituisce un percorso interno alla singola authority. Spesso rappresenta una tabella del database cui si vuole accedere ma, in generale, indica la tipologia di dati su cui agire.

Un ContentProvider, come avviene per le Activity, deve essere definito in due step:

  • creare una classe Java che estenda ContentProvider;
  • creare un nodo <provider> all’interno dell’AndroidManifest.xml. Servirà ad associare la classe Java che implementa il Provider (definita al punto precedente) e l’authority scelta per gli URI.

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.

Adattamento del database a ContentProvider

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:

  • l’elenco delle scadenze: una query che recupera tutto il contenuto della tabella;
  • inserire una nuova scadenza;
  • richiede la cancellazione di una scadenza riconosciuta mediante l’id;

e questi gli URI presso i quali potranno, rispettivamente, essere richieste:

  • content:// it.html.guida.database/scadenze/lista
  • content:// it.html.guida.database/scadenze/nuova
  • content:// it.html.guida.database/scadenze/elimina

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.

Tutte le lezioni

1 ... 30 31 32 ... 84

Se vuoi aggiornamenti su Condividere i dati con i Content Provider inserisci la tua e-mail nel box qui sotto:
Tags:
 
X
Se vuoi aggiornamenti su Condividere i dati con i Content Provider

inserisci la tua e-mail nel box qui sotto:

Ho letto e acconsento l'informativa sulla privacy

Acconsento al trattamento di cui al punto 3 dell'informativa sulla privacy