
Una delle caratteristiche che può rivelarsi molto utile nelle nostre applicazioni Android è l’utilizzo di un database relazionale interno al dispositivo.
In Android, è già compresa la libreria SQLite che – senza attivare alcun servizio continuo in background – permette di avere a disposizione un database relazionale salvato in un unico file. Sqlite è un motore di persistenza estremamente utilizzato nel mondo proprio perchè non richiede alcuna forma id installazione se non l’integrazione nel linguaggio con cui vogliamo usarlo.
Per avere a disposizione un database SQLite in un’app Android sarà sufficiente dichiararlo nel codice Java, specificando i due parametri che permettono di riconoscerlo:
- il nome del file;
- la versione di sviluppo: tracceremo il livello di evoluzione di ogni database che progetteremo con un numero intero.
Esempio passo passo
In questo post, vogliamo fornire un esempio pratico, organizzando però il discorso per gradi in modo da mostrare le varie componenti di cui abbiamo bisogno.
L’esempio mostra una semplice app (il cui codice di esempio è scaricabile qui)che ha lo scopo di memorizzare un elenco di libri cui siamo interessati.
Dovremo gestire due parti nel progetto:
- l’interfaccia utente che sarà molto semplice e non farà altro che mostrare un elenco di libri in una ListView;
- il motore di persistenza che sarà costituito dalle classi che permettono di interagire con il database.
Gestione del database
Un modo comodo per gestire il nostro dabatase Sqlite viene offerto direttamente da Android: si tratta della classe SqliteOpenHelper che si preoccuperà di cercare per noi il database su disco grazie a nome e versione che noi indicheremo e di offrircelo nel codice Java, astratto come oggetto di classe SqliteDatabase.
Proprio la classe SqliteDatabase offre al suo interno tutti i metodi per l’interazione con il database.
Questa è la nostra implementazione di SqliteOpenHelper:
public class MyHelper extends SQLiteOpenHelper { public MyHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } private void inizializza(SQLiteDatabase db) { String insert1="INSERT INTO biblio (titolo, autore, numero_pagine) " + "VALUES ('Promessi sposi','Alessandro Manzoni',500)"; String insert2="INSERT INTO biblio (titolo, autore, numero_pagine) " + "VALUES ('Il deserto dei Tartari','Dino Buzzati', 270)"; String insert3="INSERT INTO biblio (titolo, autore, numero_pagine) " + "VALUES ('Il Gattopardo','Giuseppe Tomasi di Lampedusa', 300)"; db.execSQL(insert1); db.execSQL(insert2); db.execSQL(insert3); } @Override public void onCreate(SQLiteDatabase db) { String comando="CREATE TABLE biblio (" + " _id INTEGER PRIMARY KEY AUTOINCREMENT," + " titolo TEXT," + " autore TEXT," + "numero_pagine INTEGER)"; db.execSQL(comando); inizializza(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
Come si vede esso richiede obbligatoriamente l’implementazione di tre metodi:
- un costruttore che riceva un riferimento al Context nonché il nome del database da cercare e la sua versione;
- onCreate che viene invocato solo alla prima richiesta del database nella vita dell’app ossia quando il database ancora non esiste;
- onUpgrade invocato quando su disco viene rinvenuto un file dal nome identico a quello richiesto ma di una versione meno recente. Lo lasceremo vuoto nel nostro esempio ma quando viene implementato contiene il codice necessario ad adeguare la struttura del database alla versione più nuova senza distruggere i dati al suo interno (a meno che questo non sia necessario ai fini dell’aggiornamento).
Nell’organizzazione del nostro esempio, la classe che userà l’helper non sarà direttamente l’Activity bensì un’altra, che chiameremo DbManager, all’interno della quale inseriremo tutti i metodi che faranno accesso al database.
public class DbManager { MyHelper helper=null; private final static String DATABASE="biblio"; private final static int VERSIONE_DATABASE=1; DbManager(Context context) { helper=new MyHelper(context, DATABASE, null, VERSIONE_DATABASE); } public Cursor elencoLibri() { String query="SELECT * FROM biblio"; SQLiteDatabase db= helper.getReadableDatabase(); return db.rawQuery(query, null); } }
Nel nostro esempio, il DbManager contiene solo un metodo che offre la lista completa dei libri. Si noti che l’oggetto che rappresenta il risultato dell’interrogazione è un Cursor. Questo punta ad un record del set di risultati e dispone di molti metodi:
- una serie di metodi di “movimento” permettono di cambiare il record cui stiamo puntando: prima di leggere un record sarà necessario raggiungerlo con il Cursor. Tra questi metodi ricordiamo move, moveToLast, moveToPosition, moveNext e movePrevious che permettono rispettivamente di muoversi di un certo numero di posizioni, di raggiungere il primo o l’ultimo record, di saltare ad una determinata posizione ed infine di passare al prossimo o al precedente risultato;
- metodi per la lettura dei dati. Una volta che il cursore punta ad un record si può leggere i suoi campi in accordo con il loro tipo di dato e per questo avremo getString, getInt, getFloat e altri ancora.
Da notare che i metodi di cui abbiamo parlato al secondo punto del precedente elenco richiedono come parametro in input il numero progressivo all’interno del record del campo da leggere e non il suo nome. Quindi se dovremo leggere il secondo campo che si chiamerà – poniamo il caso – “autore”, dovremo invocare sul Cursor getString(1) e non getString(“autore”): ciò non sempre aiuta la leggibilità del codice. Potremo, per questo, utilizzare il metodo getColumnIndex, sempre della classe Cursor, che convertirà il nome del campo nella sua posizione.
L’interfaccia utente
Nell’Activity, mostreremo i risultati in una semplice ListView che, per semplicità, qui creeremo direttamente nell’onCreate e ci focalizzeremo sull’uso di un particolare tipo di Adapter, specializzato nella lettura di dati da un Cursor: il SimpleCursorAdapter.
public class MainActivity extends AppCompatActivity { private ListView lv=null; private SimpleCursorAdapter adapter=null; private DbManager db=null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); lv=new ListView(this); setContentView(lv); db=new DbManager(this); adapter=new SimpleCursorAdapter( this, R.layout.row_layout, db.elencoLibri(), new String[]{"titolo","autore","numero_pagine"}, new int[]{R.id.titolo, R.id.autore, R.id.nrpagine}, 0 ); lv.setAdapter(adapter); } }
Conviene prestare un pò di attenzione agli elementi che richiede il costruttore del SimpleCursorAdapter, soprattutto dal secondo al quinto:
- R.layout.row_layout è il layout che disegna la forma che avrà ogni singola riga mostrata nella ListView. Al suo interno ci saranno una serie di TextView ognuna delle quali contraddistinta da un id: queste ospiteranno i dati letti da un singolo record prelevato dal Cursor;
- db.elencoLibri() è il Cursor con cui inizializzeremo l’adapter all’avvio dell’app;
- new String[]{“titolo”,”autore”,”numero_pagine”} è l’elenco dei campi che dovremo leggere dal Cursor;
- new int[]{R.id.titolo, R.id.autore, R.id.nrpagine} è l’elenco degli id delle TextView dichiarati in R.layout.row_layout.
Il SimpleCursorAdapter assocerà da solo i valori prelevati dai campi del record con le TextView e la regola per l’associazione sarà la corrispondenza tra il campo nominato in una posizione dell’array di String con la TextView il cui id è collocato nella corrispondente posizione nell’array di int.
Questo è il row_layout che abbiamo disegnato per l’esempio ma, ovviamente, si può modificare a proprio piacimento purchè si mantenga una corretta associazione di id e campi del record nella configurazione del SimpleCursorAdapter:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="@dimen/paddingL"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="@dimen/mediumFont" android:textStyle="bold" android:id="@+id/titolo"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/titolo" android:textSize="@dimen/mediumFont" android:id="@+id/autore"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="vertical" android:background="@color/colorBackground" android:layout_alignParentRight="true" android:layout_centerVertical="true"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="@dimen/bigFont" android:gravity="center" android:textColor="@android:color/white" android:id="@+id/nrpagine"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="@dimen/smallFont" android:textStyle="bold" android:gravity="center" android:textColor="@android:color/white" android:text="pagine"/> </LinearLayout> </RelativeLayout>
Conclusioni
Per il momento, ci fermiamo qui. Questo è il primo passo per l’integrazione di un database Sqlite all’interno di un’app Android. Abbiamo cercato di evidenziare in modo lineare i passaggi: prossimamente, approfondiremo il discorso aggiungendo ulteriori funzionalità in modo da completare l’applicazione.
Continuate a seguirci!
2 Responses to “Android app con database interno: guida passo passo”
16 Maggio 2019
gianlucadove posso trovare il file del database???
3 Aprile 2020
davideSalve, come è possibile aggiungere altri elenco direttamente dall’app e che rimangono memorizzati.
ad esempio con questa programmazione non è possibile aggiungere altri libri come dicevo prima direttamente dall’app