Room è una libreria per lavorare con SQLite in Android, parte di Jetpack. Fornisce un livello di astrazione sopra SQLite grezzo, automatizzando la creazione di tabelle, l’esecuzione di query e la conversione dei dati in oggetti Kotlin e Java. Secondo Android Developers, Room compila le query SQL al momento della build, verificando la correttezza della sintassi e le relazioni tra Entity e tabelle.
Punti chiave
Room è una libreria di persistenza di Android Jetpack che fornisce il mapping oggetto-relazionale per SQLite. Room risolve tre problemi principali di SQLite grezzo: la scrittura di grandi quantità di codice boilerplate per creare tabelle, la mancanza di verifica delle query SQL al momento della compilazione e la conversione manuale di Cursor in oggetti.
La libreria utilizza un processore di annotazioni (kapt o KSP) che genera l’implementazione delle classi astratte RoomDatabase e DAO al momento della build. Ciò garantisce che gli errori di sintassi in SQL e le discrepanze di tipo vengano scoperti prima dell’esecuzione dell’applicazione, anziché in fase di esecuzione dopo la pubblicazione su Google Play.
Secondo Google I/O 2023, Room è utilizzato nel 68% delle applicazioni Android che lavorano con dati locali. È lo standard per l’archiviazione dei dati sul dispositivo, raccomandato da Google per tutti i nuovi progetti — al posto dei obsoleti SQLiteOpenHelper e ContentProvider.
Integrate Room in progetti che richiedono caching locale dei dati dal server, modalità offline o archiviazione di dati utente strutturati con possibilità di eseguire query SQL complesse.
Room fa parte di Android Jetpack ed è ufficialmente raccomandato da Google per tutti i nuovi progetti che lavorano con dati locali. A differenza di Realm o ObjectBox, Room utilizza SQLite nativo, garantendo la compatibilità con qualsiasi strumento di database di terze parti — da DB Browser a DataGrip. Gli sviluppatori possono aprire il file .db dell’applicazione ed eseguire query SQL direttamente, semplificando il debug e l’analisi dei dati durante lo sviluppo.
Entity è una classe di dati annotata con @Entity che Room trasforma in una tabella del database. Ogni campo della classe diventa una colonna della tabella e ogni istanza diventa una riga. Room utilizza la riflessione per accedere ai campi, quindi l’annotazione @PrimaryKey è richiesta per un identificatore obbligatorio.
L’annotazione @Entity indica a Room che la classe è una tabella. Il parametro tableName imposta il nome della tabella se diverso dal nome della classe. @PrimaryKey definisce la chiave primaria con supporto alla generazione automatica tramite autoGenerate = true.
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
@Ignore
val tempData: String?
)
@ColumnInfo specifica il nome della colonna nella tabella se diverso dal nome del campo Kotlin. @Ignore esclude un campo dalla tabella — non verrà salvato nel database. @ForeignKey descrive le chiavi esterne per le relazioni tra tabelle con operazioni a cascata in caso di eliminazione o aggiornamento.
Room supporta oggetti annidati tramite l’annotazione @Embedded. I campi della classe annidata vengono espansi in colonne della tabella padre con un prefisso per evitare conflitti di nomi. Ad esempio, una classe Address con campi city e street incorporata in User creerà le colonne address_city e address_street nella tabella users, eliminando la necessità di tabelle separate per semplici oggetti valore.
Room supporta solo tipi primitivi e i loro wrapper. Per memorizzare liste, Date o tipi personalizzati, utilizzare @TypeConverter — metodi statici per convertire tra un tipo personalizzato e un primitivo SQLite, ad esempio, tra List e una stringa JSON.
DAO (Data Access Object) è un’interfaccia o classe astratta annotata con @Dao contenente metodi per l’accesso ai dati. Ogni metodo è annotato con un’operazione SQL: @Insert, @Update, @Delete o @Query con una query SQL esplicita.
L’annotazione @Query accetta una stringa SQL che Room verifica al momento della compilazione per la correttezza della sintassi e la corrispondenza dei nomi delle colonne con i campi Entity. Room supporta query parametrizzate tramite la sintassi :paramName.
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("SELECT * FROM users ORDER BY name ASC")
fun getAllUsers(): Flow<List<User>>
}
@Insert supporta le strategie OnConflictStrategy per gestire i conflitti durante l’inserimento di record duplicati. Flow come tipo di ritorno fornisce aggiornamenti reattivi dell’interfaccia utente a ogni modifica dei dati nella tabella — l’abbonamento si riavvia automaticamente a ogni INSERT, UPDATE o DELETE.
L’annotazione @Transaction garantisce l’esecuzione atomica di più operazioni in un unico blocco transazionale. Room blocca il database durante l’esecuzione, prevenendo condizioni di gara durante l’accesso concorrente da più thread.
RoomDatabase è una classe astratta che combina Entity e DAO in un unico punto di accesso al database. Viene creata tramite Room.databaseBuilder con la versione dello schema e un elenco di classi Entity. L’istanza del database dovrebbe essere creata come singleton tramite un delegato lazy per evitare connessioni multiple.
Una migrazione in Room è una classe Migration che descrive uno script SQL per la transizione da una versione vecchia dello schema a una nuova. Se non viene fornita una migrazione quando lo schema cambia, Room lancia IllegalStateException. Ciò protegge dalla perdita accidentale dei dati dell’utente durante l’aggiornamento dell’applicazione.
val migration_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
}
}
val db = Room.databaseBuilder(
getApplication(),
AppDatabase::class.java,
"app_database"
).addMigrations(migration_1_2)
.build()
Per lo sviluppo, è possibile utilizzare fallbackToDestructiveMigration, che elimina il vecchio database e ne crea uno nuovo in caso di mancata corrispondenza delle versioni. Questa modalità è destinata solo al debug — le versioni di produzione devono includere migrazioni appropriate.
Per testare il database, Room fornisce una classe speciale Room.inMemoryTestBuilder che crea un database in memoria senza salvare su disco. Dopo il completamento di ogni test, il database viene automaticamente distrutto, garantendo il completo isolamento degli scenari di test. In combinazione con la libreria android-arch-core-testing, gli sviluppatori possono gestire il ciclo di vita del database e verificare la correttezza delle migrazioni senza dover pulire manualmente lo stato.
Le prestazioni di Room dipendono direttamente dalla struttura delle query e dagli indici. Per analizzare le query lente, Room fornisce il flag enableQueryCallback, che registra tutte le query SQL con il tempo di esecuzione. Gli sviluppatori possono utilizzare questo log per trovare query che richiedono più di 100 millisecondi e ottimizzarle aggiungendo indici compositi tramite l’annotazione @Index in @Entity o riscrivendo le sottoquery come operazioni JOIN dirette con @Relation.
Room supporta anche la crittografia del database tramite SQLCipher. L’aggiunta della libreria net.zetetic:android-database-sqlcipher e l’utilizzo di SupportFactory al posto di quella standard fornisce una crittografia trasparente di tutti i dati su disco senza modificare le query DAO o la struttura Entity. Ciò è necessario per le applicazioni che gestiscono dati personali degli utenti ed è conforme al GDPR e alla legge federale russa 152-FZ sulla protezione dei dati personali. La password di crittografia può essere memorizzata in Android Keystore per proteggerla dall’estrazione tramite strumenti su dispositivi rooted.
Room supporta nativamente Kotlin Coroutines a partire dalla versione 2.1. I metodi DAO possono essere funzioni suspend che eseguono query in un thread di background senza bloccare il thread principale. Room gestisce automaticamente i dispatcher, utilizzando Dispatchers.IO per le query di lettura e scrittura.
Per query reattive, Room restituisce un Flow — un flusso di dati freddo che emette un nuovo valore a ogni modifica della tabella interessata. ViewModel si abbona al Flow tramite stateIn o collect, fornendo aggiornamenti automatici dell’interfaccia utente senza notificare manualmente l’adattatore.
Room supporta anche Paging 3 tramite un’implementazione speciale di PagingSource che carica i dati pagina per pagina da SQLite. Ciò è efficiente per grandi elenchi con migliaia di record: Paging 3 carica solo le righe visibili sullo schermo e le aggiorna automaticamente alle modifiche del database.
Utilizzare Paging 3 con Room per visualizzare feed di notizie, log di operazioni o elenchi di prodotti con accesso offline e scorrimento infinito.
Domande frequenti
Room automatizza la creazione di tabelle, la conversione Cursor-oggetti e la verifica SQL al momento della compilazione. SQLiteOpenHelper richiede di scrivere lo schema manualmente, gestire Cursor e non ha verifica delle query prima dell’esecuzione dell’applicazione, aumentando il rischio di errori.
Sì, quando si modifica un Entity (aggiunta/rimozione di un campo, modifica di un tipo), è necessaria una migrazione. Senza di essa, Room lancia IllegalStateException all’avvio. Per lo sviluppo, è possibile abilitare fallbackToDestructiveMigration, ma le versioni di produzione richiedono script di migrazione corretti.
Room supporta @ForeignKey per operazioni a cascata e @Relation per oggetti annidati. Per query JOIN complesse, utilizzare l’annotazione @Transaction con @Query che restituisce un POJO con entità annidate tramite @Embedded e @Relation.
Sì, Room è completamente compatibile con Java. Invece di funzioni suspend, utilizzare LiveData o RxJava Observable; invece di Flow, utilizzare LiveData. Room con Java supporta tutte le stesse annotazioni, ma richiede più codice boilerplate per le operazioni asincrone.
Room supporta la crittografia tramite SQLCipher di Zetetic. Invece di Room.databaseBuilder, utilizzare SupportFactory dalla libreria net.zetetic:android-database-sqlcipher, passando la password di crittografia. Tutti i dati su disco verranno crittografati in modo trasparente per le query DAO.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche