Le informazioni sono probabilmente la risorsa più importante di cui gli utenti si fidano con le app. Per gli sviluppatori di app, queste informazioni ci dicono chi è l’utente, il che ci permette di fornire una buona esperienza utente (UX). Inoltre, applicando regole di business a queste informazioni, definiamo il comportamento che l’app dovrebbe avere. Queste informazioni possono essere sensibili ed esporre i dati privati degli utenti, quindi è molto importante gestirle correttamente per garantirne l’integrità, la privacy e la corretta conservazione. All’interno di questo tutorial Android Room, vi mostreremo come è possibile lavorare con questi dati più facilmente, garantendo la loro integrità e sicurezza, il tutto utilizzando Room. Room fa parte dei componenti dell’architettura Android.
Hai bisogno di Room?
Quando si parla di memorizzare informazioni in modo persistente su Android, l’opzione più importante e “facile” è quella di utilizzare SQLite. Tuttavia, avere una buona implementazione del database usando SQLite implica la generazione di molto codice che non fornisce un reale valore. L’architettura che segue spesso non è così pulita o chiara come potrebbe essere.
Si potrebbe anche usare la mappatura oggetto-relazionale, o ORM, ma sarà comunque necessario definire e creare manualmente il database, sottoclassificando SQLiteOpenHelper e creando le classi contratto.
Quindi, la domanda è: c’è un modo per semplificare tutto questo processo? La risposta è: Sì, Room è la vostra soluzione.
Una occhiata più da vicino a Room e come funziona
Room è uno degli strumenti più importanti nei componenti architettonici di Android. Rilasciato nel Google I/O 2016, è un potente strumento per memorizzare e manipolare le informazioni sulle applicazioni Android. Fornisce un modo molto semplice per lavorare con i dati e garantisce sempre la loro sicurezza e integrità.
Room non è un ORM; invece è un’intera libreria che ci permette di creare e manipolare database SQLite più facilmente. Usando le annotazioni, possiamo definire i nostri database, tabelle e operazioni. Room tradurrà automaticamente queste annotazioni in istruzioni/query SQLite per eseguire le operazioni corrispondenti nel motore del database.
I tre componenti principali di Room sono:
– Entità: Rappresenta una tabella all’interno del database di Room. Deve essere annotato con @Entity.
– DAO: Un’interfaccia che contiene i metodi per accedere al database. Si annota con @Dao.
– Database: Rappresenta il database. È un oggetto che contiene una connessione al database SQLite, e tutte le operazioni vengono eseguite attraverso di esso. È annotato con @Database.
L’architettura della stanza si presenta così:
Troppe parole – Guardiamo un esempio
Tuffiamoci in questo tutorial di Android Room. Immaginiamo di dover creare un’applicazione per memorizzare la vostra routine in palestra. Avremo quattro entità nel nostro database, come mostreremo di seguito. Tutto il codice di esempio è scritto usando Kotlin (se non conosci Kotlin o vuoi saperne di più, ti invito a leggere il mio articolo su di esso).
La prima cosa che dobbiamo fare è aggiornare il nostro file gradle. Dovrebbe assomigliare a questo:
apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'apply plugin: 'kotlin-kapt'android { //.. Omitted since it is not relevant for the example}dependencies { //... some dependencies were omitted due to they are not relevant for the example def room_version = "2.2.0-rc01" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" compile 'com.google.code.gson:gson:2.2.4' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation 'io.reactivex.rxjava2:rxjava:2.1.17'}
Vediamo e analizziamo ognuno dei tre componenti principali di Room: Entità, DAO e Database.
Entità
Per il nostro esempio, useremo quattro entità: Genere, Esercizio, Routine e Apprendista.
Gender.kt
Rappresenta il genere dell’allievo
@Entitydata class Gender( @PrimaryKey(autoGenerate = true) val id: Int? = null, val name: String)
Cose da notare:
– Tutte le classi che rappresentano un’entità del database devono essere annotate con @Entity
– Con l’annotazione @PrimaryKey(autoGenerate = true) stiamo indicando che l’id è la chiave primaria dell’entità e dovrebbe essere autoGenerato dal motore del database.
Esercizio.kt
Rappresenta un esercizio che fa parte di una routine.
@Entitydata class Exercise( @PrimaryKey(autoGenerate = true) val exerciseId: Int, val name: String, val repetitions:Int, @ColumnInfo(name = "machine_name") val machineName: String, val liftedWeight: Int)
Cose da notare:
– Per default, Room usa i nomi dei campi come nomi delle colonne nel database. Se volete che una colonna abbia un nome diverso, aggiungete l’annotazione @ColumnInfo a un campo.
Routine.kt
Fondamentalmente un contenitore di esercizi che insieme creano una routine di esercizi.
@Entity(tableName = "traineeRoutine")data class Routine( @PrimaryKey(autoGenerate = true) val routineId: Int, @ColumnInfo(name = "due_day") val dueDay: Date, @TypeConverters(ListConverter::class) val exercises: List)
Cose da notare:
– Quando una classe è annotata con @Entity, il nome della tabella sarà il nome della classe. Se vogliamo usare un nome diverso, dobbiamo aggiungere la proprietà tableName insieme all’annotazione @Entity.
– L’annotazione @TypeConverters deve essere usata quando dichiariamo una proprietà il cui tipo è una classe personalizzata, una lista, un tipo di data o qualsiasi altro tipo che Room e SQL non sanno come serializzare. In questo caso, stiamo usando l’annotazione a livello del campo della classe, per cui solo quel campo potrà usarla. A seconda di dove l’annotazione è posizionata, si comporterà diversamente come spiegato qui.
Trainee.kt
Presenta il proprietario della routine.
@Entity(indices = , foreignKeys = , childColumns = )])data class Trainee( @PrimaryKey(autoGenerate = true) val id: Int, val name: String, val age: Int, val gender: Int?, @Embedded val routine: Routine)
DAOs
Il DAO (Data Access Objects) è usato per accedere ai nostri dati quando implementiamo Room. Ogni DAO deve includere un insieme di metodi per manipolare i dati (inserire, aggiornare, cancellare o ottenere).
Un DAO può essere implementato come interfaccia o come classe astratta. Nel nostro caso, usiamo un’interfaccia. Poiché tutti i DAO sono fondamentalmente identici, ne mostreremo solo uno.
GenderDao.kt@Daointerface GenderDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertGender(gender: Gender) @Update fun updateGender(gender: Gender) @Delete fun deleteGender(gender: Gender) @Query("SELECT * FROM Gender WHERE name == :name") fun getGenderByName(name: String): List @Query("SELECT * FROM Gender") fun getGenders(): List}
Alcune cose da notare:
– Tutti i DAO devono essere annotati con @Dao.
– Una funzione annotata con @Insert, @Update, o @Delete deve ricevere come parametro un’istanza della classe desiderata, che rappresenta l’oggetto che vogliamo rispettivamente inserire, aggiornare, o cancellare.
– Nel caso di operazioni di insert o update, possiamo usare la proprietà onConflict per indicare cosa fare quando si verifica un conflitto nell’esecuzione dell’operazione. Le strategie disponibili da utilizzare sono: REPLACE, ABORT, FAIL, IGNORE e ROLLBACK.
– Se vogliamo ottenere informazioni specifiche da una o più entità, possiamo annotare una funzione con @Query e fornire uno script SQL come parametro.
Database
Rappresenta il database. Contiene una connessione al database SQLite attuale.
AppDatabase.kt@Database(entities = , version = 1)@TypeConverters(DateTypeConverter::class)abstract class AppDatabase : RoomDatabase() { abstract fun exerciseDao(): ExerciseDao abstract fun genderDao(): GenderDao abstract fun routineDao(): RoutineDao abstract fun traineeDao(): TraineeDao companion object { var INSTANCE: AppDatabase? = null fun getAppDataBase(context: Context): AppDatabase? { if (INSTANCE == null){ synchronized(AppDatabase::class){ INSTANCE = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "myDB").build() } } return INSTANCE } fun destroyDataBase(){ INSTANCE = null } }}
Cose da notare qui:
– Questa è una classe astratta che deve estendere RoomDatabase.
– Deve essere annotata con @Database, e riceve una lista di entità con tutte le classi che compongono il database (tutte queste classi devono essere annotate con @Entity). Dobbiamo anche fornire una versione del database.
– Dobbiamo dichiarare una funzione astratta per ciascuna delle entità incluse nell’annotazione @Database. Questa funzione deve restituire il DAO corrispondente (una classe annotata con @Dao).
– Infine, dichiariamo un oggetto compagno per ottenere l’accesso statico al metodo getAppDataBase, che ci dà un’istanza singleton del database.
Type Converters
I convertitori di tipo si usano quando dichiariamo una proprietà che Room e SQL non sanno come serializzare. Vediamo un esempio di come serializzare un tipo di dati ‘data’.
DateTypeConverter.ktclass DateTypeConverter { @TypeConverter fun fromTimestamp(value: Long?): Date? { return if (value == null) null else Date(value) } @TypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time }}
Utilizzare il database Room
Ora vediamo un esempio molto semplice di come usare il database Room che abbiamo appena creato:
MainActivity.ktclass MainActivity : AppCompatActivity() { private var db: AppDatabase? = null private var genderDao: GenderDao? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Observable.fromCallable({ db = AppDatabase.getAppDataBase(context = this) genderDao = db?.genderDao() var gender1 = Gender(name = "Male") var gender2 = Gender(name = "Female") with(genderDao){ this?.insertGender(gender1) this?.insertGender(gender2) } db?.genderDao()?.getGenders() }).doOnNext({ list -> var finalString = "" list?.map { finalString+= it.name+" - " } tv_message.text = finalString }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe() }}
Cosa stiamo facendo?
– Ottenere l’istanza del database e GenderDao.
– Creare due istanze di Gender: Maschio e Femmina.
– Inserire le due istanze create nel database.
– Interrogare il database per ottenere tutti i generi memorizzati in esso.
– Unire il nome di tutti i generi che abbiamo ottenuto dal database, e impostare il testo della TextView con quel valore.
Room per fare di più con meno
Room è uno degli elementi importanti dei componenti dell’architettura Android. Ci dà un quadro molto robusto per lavorare con informazioni persistenti, garantendo sempre la sicurezza e l’integrità dei dati. Fornisce anche facilità d’uso agli sviluppatori, in modo che possano scrivere codice leggibile e autoesplicativo. Se volete fare di più con meno codice, e anche garantire la sicurezza dei dati dell’utente, dovreste usare Room come strato di persistenza della vostra app.
E questo è tutto! Questo è quasi tutto quello che dovete sapere per creare e utilizzare un database su Android con Room. Trovate l’intero codice di questo progetto qui. Grazie per aver letto questo tutorial su Android Room!