Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

SQLite Android: usare il database nelle applicazioni

Come gestire le informazioni conservate in un database in un applicativo Android: manipolazioni di dati in SQLite e Content provider.
Come gestire le informazioni conservate in un database in un applicativo Android: manipolazioni di dati in SQLite e Content provider.
Link copiato negli appunti

La piattaforma Android fornisce diversi metodi e strumenti per salvare i dati delle nostre applicazioni in modo persistente.  Se volessimo equipaggiare la nostra applicazione con l'attivazione/disattivazione del suono di sottofondo, oppure con la scelta della lingua da utilizzare o ancora con la possibilità di scegliere il formato della data, potremmo implementare un menu di preferenze ed utilizzare le Shared Preferences per salvare in maniera persistente le scelte dell'utente, senza doverci inventare nulla di nuovo, ma, anzi, utilizzando solamente le funzionalità messe a disposizione nativamente dalla piattaforma Android.

Le altre metodologie di memorizzazione persistente dei dati sono:

  • Internal Storage: permette di salvare dati con accesso private nella memoria del device
  • External Storage: permette di salvare i dati con accesso public in una memoria esterna condivisa (es. SD card)
  • Network Connection: permette di memorizzare i dati sul web attraverso connessioni di rete
  • Database SQLlite: permette di salvare dati strutturati in un database SQLite private

In questo articolo vedremo in dettaglio come utilizzare un database SQLite non solo per memorizzare in modo persistente i dati della nostra applicazione, ma anche per recuperarli dalla base di dati e renderli disponibili nell'applicazione stessa. Per tutti coloro che sono alle prime armi rimandiamo alla guida Android per le argomentazioni introduttive, come la configurazione dell'ambiente di sviluppo e la creazione di un progetto.

SQLite

SQLite è un leggerissimo database engine transazionale che occupa poco spazio in memoria e sul disco (da circa 4KiB a circa 350KiB, a seconda del target della piattaforma), pertanto è la tecnologia perfetta per creare e gestire database in un ambiente come quello degli applicativi mobile, dove le risorse sono molto limitate e dunque è molto importante ottimizzarne l'utilizzo.

A differenza della maggior parte degli altri database SQL, SQLite non ha un processo server separato ma legge e scrive direttamente su file ordinari sul disco: possiede praticamente tutti gli strumenti principali che caratterizzato i più importanti database SQL (tabelle, viste, indici, trigger) ed il codice è distribuito gratuitamente sia per scopi commerciali che privati.

SQLite è più diffuso di quanto ci si aspetterebbe, infatti viene utilizzato in moltissimi applicativi e device che utilizziamo quotidianamente, come l'iPhone della Apple, Mozilla Firefox, negli smartphone Symbian, in Skype, in diversi applicativi PHP o Adobe AIR, e in molti altri progetti.

Un database SQLite è, nella pratica, un file: possiamo spostarlo, copiarlo in un altro sistema (ad esempio dallo smartphone al nostro pc) e continuerà a funzionare tutto regolarmente.

SQLite e Android

Android memorizza i file seguendo uno schema preciso; il file SQLite del database di ogni applicazione viene infatti memorizzato in una directory il cui percorso è: /data/data/packagename/databases dove “packagename” è il nome del package del corrispodente applicativo.

Per visionare, modificare o cancellare i database abbiamo a disposizione due strumenti molto potenti. Il primo è utilizzare l'adb direttamente da linea di comando, mentre il secondo è utilizzare la vista File Explorer di Eclipse accessibile da Window / Show View / Other... / Android / File Explorer.

Per accedere al file del database dalla nostra app abbiamo ovviamente a disposizione gli statements SQL. Grazie alle classi che vedremo e che permettono di implementare diversi helper e adapter, Android nasconde allo sviluppatore parte del lavoro, ma per utilizzare i database è in effetti necessario avere una conoscenza almeno basilare di SQL.

Content Provider e i database Android

Alcune delle componenti principali con le quali Android semplifica la vita dello sviluppatore per quanto riguarda la gestione dei database (e non solo!) sono i Content Providers.

I Content Providers offrono delle interfacce generiche per qualsiasi data source disaccoppiando il livello di memorizzazione dei dati con quello dell'applicazione.

È molto importante evidenziare fin da subito che per default l'accesso al database è permesso solo all'applicazione che lo ha creato. È principalmente per questo motivo che i Content Providers sono molto importanti: essi offrono un'interfaccia standardizzata che le nostre applicazioni possono utilizzare per condividere ed utilizzare dati provenienti da altre applicazioni, inclusi i dati gestiti dalle applicazioni native (i.e. i contatti della nostra rubrica).

Riassumento, in Android la persistenza dei dati strutturati è fornita attraverso il seguente meccanismo:

  • Database SQLite: ogni applicazione Android può creare i propri database sui quali ha il controllo completo
  • Content Provider: offrono una generica ma moto ben definita interfaccia per utilizzare e condividere i dati

Finite le presentazioni dei principali attori coinvolti, passiamo allo sviluppo.

La classe helper

È buona pratica creare una classe helper e una classe adapter per semplificare le interazioni con il database ed introdurre così un livello di astrazione che fornisca metodi intuitivi, flessibili e robusti per inserire, eliminare e modificare i record del database.

Come vedremo tra poco in dettaglio, la classe helper è anche il luogo ideale in cui memorizzare le costanti statiche come il nome della tabella e delle colonne mentre la classe adapter dovrebbe fornire query ed esporre metodi per la creazione del database e la gestione delle connessioni (apertura e chiusura) ad esso.

Nel codice seguente vediamo l'implementazione della classe helper che permette di creare un semplice database di contatti:

public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "mydatabase.db";
private static final int DATABASE_VERSION = 1;
// Lo statement SQL di creazione del database
private static final String DATABASE_CREATE = "create table contact (_id integer primary key autoincrement, name text not null, surname text not null, sex text not null, birth_date text not null);";
// Costruttore
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
// Questo metodo viene chiamato durante la creazione del database
@Override
public void onCreate(SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE);
}
// Questo metodo viene chiamato durante l'upgrade del database, ad esempio quando viene incrementato il numero di versione
@Override
public void onUpgrade( SQLiteDatabase database, int oldVersion, int newVersion ) {
database.execSQL("DROP TABLE IF EXISTS contact");
onCreate(database);
}
}

Anche se non ci sono particolari restrizioni è consigliabile organizzare il codice fin da subito in maniera ordinata e coerente: i progetti Android fanno in fretta a crescere di dimensioni ed è perciò di vitale importanza partire con le idee ben chiare.

Per questi motivi è molto importante organizzare il codice in package che semplifichino e agevolino la manutenibilità del progetto: dunque il consiglio è di creare un package come ad esempio (utils o database) in cui salvare tutte le classi specifiche che si occupano della comunicazione e della gestione con/del database. La prima classe che possiamo creare all'interno del nuovo package è DatabaseHelper.

SQLite Open Helper: codice

Come indica il nome, questa classe implementa l'helper di cui abbiamo parlato precedentemente: vediamo finalmente in dettaglio il codice.

La classe (rigo 1) estende SQLiteOpenHelper, una classe astratta utilizzata per implementare al meglio il pattern per la creazione, l'aggiornamento e la gestione delle base di dati. Implementando un helper di tipo SQLiteOpenHelper stiamo nascondendo la logica utilizzata per decidere se il database necessita di essere creato o aggiornato prima di essere aperto: questo è il primo livello di astrazione che ci fornisce maggiore flessibilità e manutenibilità.

Estendere SQLiteOpenHelper significa dover eseguire l'override del costruttore, del metodo onCreate() e del metodo onUpgrade(), che si occupano rispettivamente della creazione e dell'updgrade del database.

onCreate() (rigo 16) non fa altro che eseguire lo script SQL di creazione della base di dati definito nella costante statica di cui abbiamo parlato poc'anzi, mentre il metodo onUpgrade() (rigo 22), in questa prima versione, effettua semplicemente il DROP (eliminazione) della tabella esistente (contact nel nostro esempio) e la sostituisce con la nuova definizione della stessa richiamando il metodo onCreate(). Una versione di questo metodo leggermente più complessa ma anche più corretta sarebbe quella che implementa la migrazione dei dati dalla tabella esistente alla nuova.

Nell'helper abbiamo anche definito tre costanti di classe (rigo 3, 4 e 7) che ci servono per memorizzare rispettivamente il nome del database che verrà creato (mydatabase.db), la versione dello stesso (nel nostro esempio impostata al valore 1) e lo statement SQL di creazione.

La classe adapter

Proseguiamo analizzando la classe che definisce l'adapter:

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.provider.ContactsContract;
public class DbAdapter {
@SuppressWarnings("unused")
private static final String LOG_TAG = DbAdapter.class.getSimpleName();
private Context context;
private SQLiteDatabase database;
private DatabaseHelper dbHelper;
// Database fields
private static final String DATABASE_TABLE = "contact";
public static final String KEY_CONTACTID = "_id";
public static final String KEY_NAME = "name";
public static final String KEY_SURNAME = "surname";
public static final String KEY_SEX = "sex";
public static final String KEY_BIRTH_DATE = "birth_date";
public DbAdapter(Context context) {
this.context = context;
}
public DbAdapter open() throws SQLException {
dbHelper = new DatabaseHelper(context);
database = dbHelper.getWritableDatabase();
return this;
}
public void close() {
dbHelper.close();
}
private ContentValues createContentValues(String name, String surname, String sex, String birth_date ) {
ContentValues values = new ContentValues();
values.put( KEY_NAME, date );
values.put( KEY_SURNAME, flag_send_sms );
values.put( KEY_SEX, text_send_sms );
values.put( KEY_BIRTH_DATE, date_sent_sms );
return values;
}
//create a contact
public long createContact(String name, String surname, String sex, String birth_date ) {
ContentValues initialValues = createContentValues(name, surname, sex, birth_date);
return database.insertOrThrow(DATABASE_TABLE, null, initialValues);
}
//update a contact
public boolean updateContact( long contactID, String name, String surname, String sex, String birth_date ) {
ContentValues updateValues = createContentValues(name, surname, sex, birth_date);
return database.update(DATABASE_TABLE, updateValues, KEY_ CONTACTID + "=" + contactID, null) > 0;
}
//delete a contact
public boolean deleteContact(long contactID) {
return database.delete(DATABASE_TABLE, KEY_ CONTACTID + "=" + contactID, null) > 0;
}
//fetch all contacts
public Cursor fetchAllContacts() {
return database.query(DATABASE_TABLE, new String[] { KEY_CONTACTID, KEY_NAME, KEY_SURNAME, KEY_SEX, KEY_BIRTH_DATE}, null, null, null, null, null);
}
//fetch contacts filter by a string
public Cursor fetchContactsByFilter(String filter) {
Cursor mCursor = database.query(true, DATABASE_TABLE, new String[] {
KEY_CONTACTID, KEY_NAME, KEY_SURNAME, KEY_SEX, KEY_BIRTH_DATE },
KEY_NAME + " like '%"+ filter + "%'", null, null, null, null, null);
return mCursor;
}

Codice 1.2

La classe DbAdapter fornisce l'interfaccia diretta per manipolare il database. L'adapter è, nella pratica, un livello di astrazione: fornisce tutta una serie di metodi che, una volta testati opportunamente, permettono al programmatore di concentrare le proprio energie sugli aspetti importanti dell'applicativo e non sulla creazione o sulla modifica di un record del database.

Inizialmente definiamo alcune proprietà e alcune costanti che utilizzeremo massiciamente all'interno della classe: la prima costante definisce il nome della tabella mentre le altre definiscono il nome di ogni colonna della stessa. Da questo momento in poi per l'implementazione dell'adapter utilizzeremo queste costanti nelle query, in modo da poterne modificare il nome in caso di necessità senza dover riscrivere tutte le query.

Segue la definizione del costruttore: l'unico elemento che sicuramente dobbiamo configurare all'interno del costruttore è il context, che passeremo di volta in volta quando dovremo istanziare un nuovo adapter per eseguire delle query.  In ambiente Android il context è un elemento fondamentale che utilizzeremo spesso nelle nostre applicazioni: è una classe astratta di cui l'implementazione è fornita dal sistema. Il context permette di accedere alle risorse specifiche dell'applicazione, come ad esempio le activity, gli intent, ecc.

Proseguiamo implementando i metodi open() e close() (righi 33 e 40). Sono metodi che useremo ogni volta che dovremmo comunicare con il database: sarà sufficiente chiamare questi metodi per lavorare con il database, nascondendo tutto quanto abbiamo visto finora.

Nel metodo open() istanziamo un oggetto di tipo DatabaseHelper (vedi codice dell'helper alla pagina precedente) che, come abbiamo visto, fornisce l'interfaccia di creazione/aggiornamento/gestione del database. A questo punto non ci rimane che richiamare il metodo getWritetableDatabase() (rigo 36) definito in SQLiteOpenHelper, il quale restituisce un oggetto database in modalità lettura/scrittura attivo fino a quando non viene richiamato il metodo close(). La prima volta che viene richiamato getWritableDatabase() il database viene aperto e vengono automaticamente richiamati i metodi onCreate(), onUpgrade() e se necessario anche il metodo onOpen().

Il metodo close() invece non fa altro che richiamare il metodo close() della classe SQLiteOpenHelper, il quale chiude ogni oggetto database aperto. Ogni volta che utilizziamo il database è buona norma chiudere le comunicazioni subito dopo aver finito di lavorare con la base di dati: questa viene messa nella cache, per cui non è un problema chiudere ed eventualmente riaprire il database ogni qualvolta sia necessario. Nello sviluppo mobile questo atteggiamento è importante: le risorse dei dispositivi non sono molte e bisogna far molta attenzione a come le utilizziamo.

Prima di passare all'implementazione vera e propria dei metodi che si occupano di interrogare la base di dati c'è un altro metodo che possiamo implementare per migliorare l'efficienza e la comodità di utilizzo dell'adapter: createContentValues() (rigo 46). Questo metodo ha un compito molto semplice: memorizzare un insieme di valori che il ContentResolver può processare per fornire l'accesso applicativo al modello del contenuto.

Quando abbiamo la necessità di accedere ai dati di un Content provider utilizziamo l'oggetto ContentResolver nel context della nostra applicazione per comunicare con il provider. Il ContentResolver comunica con un'istanza di una classe che implementa ContentProvider: questo oggetto riceve richieste dai client, gestisce la richiesta e restituisce i risultati.

I Content provider gestiscono l'accesso ad un insieme di dati strutturati: essi sono interfacce standard che connettono dati di un processo con il codice che sta “girando” in un altro. Sono componenti di estrema importanza che possiamo utilizzare quando abbiamo bisogno di un insieme di dati composto da informazioni dei nostri contatti personali, ma anche video, immagini, audio.

Chiudiamo la parentesi sui ContentProvider e proseguiamo l'analisi della classe adapter. Ritorniamo ancora un momento sul metodo createContentValues(): come abbiamo appena visto questo definisce l'insieme di dati che possiamo utilizzare nelle nostre query.  Supponiamo di voler implementare una rubrica all'interno di una nostra applicazione: allora nel nostro esempio ogni contatto sarà caratterizzato da un nome (name), un cognome (surname), un sesso (sex) e una data di nascita (birth_date). Questo sarà l'insieme di dati che manipoleremo con le query che ora analizzeremo in dettaglio.

Quelli che vedremo di seguito sono i metodi che forniscono l'interfaccia diretta per la manipolazione della base di dati. Queste funzioni nascondono tutto quanto abbiamo visto finora semplificandoci notevolmente la vita in fase di programmazione: quando avremo la necessità di creare, modificare o eliminare un contatto nella nostra applicazione, l'unica preoccupazione sarà scegliere il metodo più appropriato per soddisfare la nostra richiesta.

Creare un record

Il primo metodo che vediamo è quello che implementa l'operazione di creazione di un contatto (per comodità lo riporto di seguito):

public long createContact(String name, String surname, String sex, String birth_date ) {
ContentValues initialValues = createContentValues(name, surname, sex, birth_date);
return database.insertOrThrow(DATABASE_TABLE, null, initialValues);
}

createContact() richiede 4 parametri, chiaramente gli stessi che abbiamo visto poc'anzi quando abbiamo analizzato in dettaglio il metodo createContentValues(), ovvero il nome, il cognome, il sesso e la data di nascita. Quando abbiamo definito lo script per la creazione della tabella (vedi il codice dell' helper) abbiamo dichiarato una chiave primaria auto incrementale (_id), per cui durante la fase di creazione di un contratto verrà automaticamente incrementato questo valore per rendere unicamente identificabile ogni record.

Dopo aver richiamato createContactValues(), il metodo utilizza l'istanza SQLiteDatabse database correttamente impostata nel metodo open() per richiamare la funzione insertOrThrow(): questa permette di inserire un record nel database e come parametri si aspetta il nome della tabella in cui inserire la riga, un valore che indica come comportarsi nel caso in cui i valori iniziali siano vuoti ed infine i valori da inserire. Il secondo parametro stabilisce il comportamento della insert nel caso in cui initialValues sia vuoto: se non impostato a null, il parametro indica in quali colonne vogliamo che sia inserito un null lì dove initialValues è vuoto.

Il metodo insertOrThrow() restituisce l'id ovvero la chiave primaria del record appena creato o il valore -1 in caso di errore.

Aggiornare un record

Aggiornare un contatto è un'operazione molto simile alla creazione:

public boolean updateContact( long contactID, String name, String surname, String sex, String birth_date ) {
ContentValues updateValues = createContentValues(contactID, name, surname, sex, birth_date);
return database.update(DATABASE_TABLE, updateValues, KEY_CONTACTID + "=" + contactID, null) > 0;
}

Codice 1.4

Il metodo updateContact() richiede gli stessi 4 parametri (name, surname, sex, birth_date) del metodo createContact(), più un parametro che indica l'identificatore del contatto da aggiornare, contactID.

Come nel caso precedente, la prima cosa che facciamo è richiamare il metodo createContentValues(), e successivamente la funzione update() della classe SQLiteDatabase sull'istanza database. Questo metodo ci fornisce uno strumento molto semplice e potente per aggiornare la nostra base di dati: richiedere 3 parametri, il nome della tabella, l'oggetto ContentValues che contiene i valori del contatto da aggiornare e infine dobbiamo indicare la clausola where. Nel codice 1.4 la clausola where è la seguente:

KEY_CONTACTID + "=" + contactID

Codice 1.5

che equivale alla stringa "_id = valore_passato_come_paramentro". La clausola where è opzionale perchè possiamo anche decidere di passare null invece che impostare una clausola specifica: in questo caso verranno aggiornate tutti i record della tabella.

Il metodo restituisce il numero di record modificati: nell'esempio il risultato potrebbe essere 1 nel caso in cui nella tabella esista il contatto con identificatore uguale al valore che gli abbiamo passato come parametro, altrimenti sarebbe 0.

Eliminare un record

Il metodo deleteContact() è ancora più semplice ed intuitivo del precedente:

public boolean deleteContact(long contactID) {
return database.delete(DATABASE_TABLE, KEY_CONTACTID + "=" + contactID, null) > 0;
}

Codice 1.6

Questa funzione accetta un parametro, ovvero l'id del contatto da eliminare, e richiama il metodo delete() della classe SQLiteDatabase: questo non fa altro che eliminare il record con identificativo uguale al valore passato come parametro; passando invece null cancelleremo tutte le righe della tabella.

Il metodo restituisce il numero di righe cancellate, o il valore "0" se non sono stati trovati record corrispondenti. Se vogliamo cancellare tutte le righe della tabella e conoscere il loro numero possiamo utilizzare il valore "1" nella clausola where.

Gli ultimi due metodi che vediamo sono due esempi di query per l'interrogazione del database: fetchAllContacts() e fetchContactsByFilter().

fetchAllContacts() è un metodo che permette di recuperare tutti i contatti presenti nel nostro database:

public Cursor fetchAllContacts() {
return database.query(DATABASE_TABLE, new String[] { KEY_CONTACTID, KEY_NAME, KEY_SURNAME, KEY_SEX, KEY_BIRTH_DATE}, null, null, null, null, null);
}

Codice 1.7

Come mostra il codice 1.7, la funzione è composta da una solo riga di codice: come negli altri casi utilizziamo l'istanza SQLiteDatabase database per richiamare la funzione specifica messa a disposizione dalla piattaforma Android, in questo caso a noi interessa la funzione query(). Questa funzione, in base ai parametri passati, genera le query necessarie per interrogare il database e recuperare i dati che ci interessano. Il numero dei parametri di configurazioni varia in base a quale funzione query() abbiamo la necessità di utilizzare: la piattaforma Android infatti mette a disposizione 3 funzioni query(), che accettano rispettivamente 7, 8 e 9 parametri, in base appunto al tipo di query di cui abbiamo bisogno.

Nel nostro esempio abbiamo utilizzato la funzione query() che richiede 7 parametri (le altre due disponibili permettono rispettivamente di impostare anche un limite e un limite più un flag per l'attivazione del DISTINCT SQL):

  1. il nome della tabella in cui deve essere eseguita la query;
  2. la lista delle colonne da restituire;
  3. un filtro per stabilire quali righe restituire (corrisponde alla clausola SQL WHERE);
  4. un array di stringhe per inserire dinamicamente alcuni valori nella SELECT;
  5. un filtro che corrisponde alla clausola SQL GROUP BY;
  6. un filtro che corrisponde alla clausola HAVING;
  7. un filtro che corrisponde alla clausola ORDER BY.

La funzione query() restituisce un oggetto di tipo Cursor: questo oggetto fornisce l'accesso in modalità di lettura-scrittura al result set restituito dalla query. Come vedremo tra poco, sarà sufficiente ciclare sull'oggetto Cursor per avere accesso ai dati ottenuti.

Il metodo fetchAllContacts() può essere un buon punto di partenza per implementare altri metodi più specifici e che sicuramente ci saranno utili in futuro, come ad esempio il seguente:

public Cursor fetchContact( long contactID ) throws SQLException {
Cursor mCursor = database.query(true, DATABASE_TABLE, new String[] {
KEY_CONTACTID, KEY_NAME, KEY_SURNAME, KEY_SEX, KEY_BIRTH_DATE
},
KEY_ CONTACTID + "=" + contactID, null, null, null, null, null);
return mCursor;
} 

Codice 1.8

fetchContact() è una funzione molto simile alla precedere, ma ho aggiunto un parametro in più per implementare la clausola WHERE. Come mostrato nel codice 1.8, la funzione richiede un parametro (contactID di tipo long) per passare alla query l'identificatore del record di cui voglio recuperare i valori: questo parametro lo utilizziamo nella clausola WHERE per "stringere" il result set al solo record di nostro interesse. La clausola è la seguente:

KEY_CONTACTID + "=" + contactID

Nella pratica il codice 1.8 genera una stringa di questo tipo "_id = value" dove value è il parametro che gli abbiamo passato. In questo modo, partendo dal metodo fetchAllContacts(), abbiamo esteso il nostro adapter che ora fornisce la possibilità di interrogare il database per trovare le informazioni di uno specifico contatto.

fetchContactsByFilter() è un altro esempio di come, partendo dalla funzione fetchAllContatcs(), possiamo implementare nuovi metodi per rendere il nostro adapter sempre più potente.

public Cursor fetchContactsByFilter(String filter) {
Cursor mCursor = database.query(true, DATABASE_TABLE, new String[] {
KEY_CONTACTID, KEY_NAME, KEY_SURNAME, KEY_SEX, KEY_BIRTH_DATE },
KEY_NAME + " like '%"+ filter + "%'", null, null, null, null, null);
return mCursor;
}

Codice 1.9

Anche questa funzione, come la precedente, accetta un parametro, ma in questo caso è una stringa che verrà utilizzata come filtro. Infatti, sempre servendoci nella clausola WHERE, utilizziamo il parametro per generare una stringa di ricerca da applicare al nome del contatto, in modo che il result set sia composto da tutti i contatti che hanno un nome contenente la stringa filter passata come parametro.

Come abbiamo visto diventa relativamente semplice implementare un adapter molto potente e flessibile capace di semplificarci notevolmente lo sviluppo della nostra app. Abbiamo sostanzialmente coperto i casi basilari di utilizzo dei database in Android, non ci rimane che vedere come utilizzare in un'activity quanto appreso finora.

Supponiamo di avere nel progetto un'activity in cui vogliamo visualizzare tutta la lista dei contatti che abbiamo memorizzato nella nostra applicazione:

public class ListAll extends Activity {
[...]
private DbAdapter dbHelper;
private Cursor cursor;
[...]
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listAll);
[...]
dbHelper = new DbAdapter(this);
dbHelper.open();
cursor = dbHelper.fetchAllContacts();
dbHelper.close();
startManagingCursor(cursor);
while ( cursor.moveToNext() ) {
String contactID = cursor.getString( cursor.getColumnIndex(DbAdapter.KEY_CONTACTID) );
Log.debug(TAG, "contact id = " + contactID);
}
cursor.close();
[...]
}

Codice 1.10

La prima cosa da fare è definire due variabili di classe, rispettivamente per memorizzare l'istanza dell'helper e l'istanza del cursor.

Nel codice 1.10 ho indicato le parti che ci interessano maggiormente per chiudere il cerchio e vedere nella pratica come utilizzare quanto finora implementato: pertanto dove vedete il simbolo [...] dovete immaginare che ci sia tutto il resto del codice per far funzionare correttamente l'Activity secondo le vostre esigenze.

Nel metodo onCreate(), ovvero dove l'Activity viene creata, creiamo l'istanza dell'helper e apriamo la connessione al database con il metodo open() (vedere codice 1.2):

[...]
dbHelper = new DbAdapter(this);
dbHelper.open();
[...]

Ora possiamo utilizzare l'helper per richiamare i metodi che abbiamo precedentemente implementato: richiamiamo ad esempio il metodofetchAllContacts() (vedere codice 1.7) che come abbiamo visto restiutisce un oggetto di tipo Cursor, e subito dopo utilizziamo il metodo close() per liberare le risorse che abbiamo occupato per la query:

[...]
cursor = dbHelper.fetchAllContacts();
dbHelper.close();
[...]

A questo punto abbiamo a disposizione i dati recuperati dal database, e possiamo utilizzarli all'interno della nostra applicazione. Ad esempio possiamo stampare nei log gli identificatori dei contatti ciclando sull'oggetto Cursor:

[...]
while ( cursor.moveToNext() ) {
String contactID = cursor.getString( cursor.getColumnIndex(DbAdapter.KEY_CONTACTID) );
Log.debug(TAG, "contact id = " + contactID);
}
cursor.close();
[...]

Il metodo moveToNext() muove il cursore alla riga successiva, dalla quale possiamo ricavare il valore che ci interessa attraverso il metodo getColumnIndex(), che restituisce l'indice della colonna richiesta, e il metodo getString(), che invece ritorna come stringa il valore della colonna richiesta.

A questo punto chiudiamo il cursore con il metodo close(), che rilascia tutte le risorse invalidandolo completamente.

Conclusione sui database Android

In questo articolo abbiamo visto come possiamo gestire il nostro database in ambiente Android, quali strumenti abbiamo a disposizione per effettuare le operazioni principali di creazione, modifica, aggiornamento di record e di tabelle e un esempio di come applicare alcuni principi per una corretta impostazione nello sviluppo e nella progettazione delle app.

Equipaggiando la nostra app con una base di dati, abbiamo un potente strumento da utilizzare per realizzare le nostre idee: l'unico limite è la nostra fantasia.

ORM

Gli ORM, come è noto, "traducono" operazioni fatte sugli oggetti in query per i database relazionali. Il ché ci consente di lavorare senza preoccuparci troppo di cosa succede nello strato di persistenza. Anche per lo sviluppo su Android possiamo sfruttare layer come questi. Per questo abbiamo dedicato un articolo all'argomento:

>> Leggi Android e gli ORM

Ti consigliamo anche