Room este o bibliotecă pentru lucrul cu SQLite în Android, care face parte din Jetpack. Oferă un strat de abstractizare peste SQLite brut, automatizând crearea tabelelor, executarea interogărilor și conversia datelor în obiecte Kotlin și Java. Conform Android Developers, Room compilează interogările SQL în faza de construire, verificând corectitudinea sintaxei și a relațiilor dintre Entity și tabele.
Principalele puncte
Room — este o bibliotecă de persistență din Android Jetpack care oferă mapare obiect-relațională pentru SQLite. Room rezolvă trei probleme principale ale SQLite brut: scrierea unui volum mare de cod boilerplate pentru crearea tabelelor, lipsa verificării interogărilor SQL la compilare și conversia manuală a Cursor în obiecte.
Biblioteca folosește compilatorul de adnotări (kapt sau KSP), care generează implementarea claselor abstracte RoomDatabase și DAO în faza de construire. Acest lucru garantează că erorile de sintaxă în SQL și nepotrivirile de tip sunt detectate înainte de lansarea aplicației, nu în runtime după publicarea în Google Play.
Conform Google I/O 2023, Room este utilizat în 68% din aplicațiile Android care lucrează cu date locale. Este standardul de stocare a datelor pe dispozitiv, recomandat de Google pentru toate proiectele noi — în locul învechitelor SQLiteOpenHelper și ContentProvider.
Implementați Room în proiecte care necesită cache local al datelor de pe server, mod offline sau stocarea datelor structurate ale utilizatorului cu posibilitatea de interogări SQL complexe.
Room face parte din Android Jetpack și este recomandat oficial de Google pentru toate proiectele noi care lucrează cu date locale. Spre deosebire de Realm sau ObjectBox, Room folosește SQLite nativ, ceea ce garantează compatibilitatea cu orice instrumente terțe pentru lucrul cu baza de date — de la DB Browser la DataGrip. Dezvoltatorul poate deschide fișierul .db al aplicației și poate executa interogări SQL direct, simplificând depanarea și analiza datelor în procesul de dezvoltare.
Entity — este o clasă de date adnotată cu @Entity, pe care Room o transformă într-un tabel al bazei de date. Fiecare câmp al clasei devine o coloană a tabelului, iar fiecare instanță — un rând. Room folosește reflecția pentru accesul la câmpuri, de aceea este necesară adnotarea @PrimaryKey pentru un identificator obligatoriu.
Adnotarea @Entity informează Room că clasa este un tabel. Parametrul tableName stabilește numele tabelului dacă diferă de numele clasei. @PrimaryKey definește cheia primară cu posibilitatea de auto-generare prin 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 stabilește numele coloanei în tabel dacă diferă de numele câmpului Kotlin. @Ignore exclude câmpul din tabel — nu va fi salvat în baza de date. @ForeignKey descrie cheile externe pentru relații între tabele cu operații în cascadă la ștergere sau actualizare.
Room suportă obiecte imbricate prin adnotarea @Embedded. Câmpurile clasei imbricate sunt desfăcute în coloane ale tabelului părinte cu un prefix pentru evitarea conflictelor de nume. De exemplu, clasa Address cu câmpurile city și street, încorporată în User, va crea coloanele address_city și address_street în tabelul users, eliminând necesitatea creării de tabele separate pentru obiecte-valoare simple.
Room suportă doar tipuri primitive și învelișurile lor. Pentru stocarea listelor, Date sau a tipurilor personalizate se folosește @TypeConverter — metode statice de conversie între tipul personalizat și primitivul SQLite, de exemplu, între List și un șir JSON.
DAO (Data Access Object) — este o interfață sau clasă abstractă adnotată cu @Dao, care conține metode de acces la date. Fiecare metodă este adnotată cu o operație SQL: @Insert, @Update, @Delete sau @Query cu o interogare SQL explicită.
Adnotarea @Query primește un șir SQL care este verificat de Room la compilare pentru corectitudinea sintaxei și corespondența numelor coloanelor cu câmpurile Entity. Room suportă interogări parametrizate prin sintaxa :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 suportă strategiile OnConflictStrategy pentru gestionarea conflictelor la inserarea înregistrărilor duplicate. Flow ca tip de returnare asigură actualizarea reactivă a UI la fiecare modificare a datelor din tabel — abonamentul se repornește automat la orice INSERT, UPDATE sau DELETE.
Adnotarea @Transaction garantează executarea atomică a mai multor operații într-un singur bloc tranzacțional. Room blochează baza de date pe durata execuției, prevenind condițiile de cursă la accesul concurent din mai multe fire de execuție.
RoomDatabase — este o clasă abstractă care unește Entity și DAO într-un singur punct de acces la baza de date. Se creează prin Room.databaseBuilder cu specificarea versiunii schemei și a listei claselor Entity. Se recomandă crearea instanței bazei de date ca singleton prin delegat lazy pentru evitarea conexiunilor multiple.
Migrarea în Room — este o clasă Migration care descrie scriptul SQL pentru trecerea de la versiunea veche a schemei la cea nouă. Dacă migrarea nu este furnizată la modificarea schemei, Room aruncă IllegalStateException. Acest lucru protejează împotriva pierderii accidentale a datelor utilizatorului la actualizarea aplicației.
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()
Pentru dezvoltare se poate folosi fallbackToDestructiveMigration, care șterge baza veche și creează una nouă la nepotrivirea versiunilor. Acest mod este destinat doar pentru depanare — în versiunile de producție se scriu obligatoriu migrări.
Pentru testarea bazei de date, Room oferă o clasă specială Room.inMemoryTestBuilder, care creează baza de date în memoria RAM fără salvare pe disc. După finalizarea fiecărui test, baza este distrusă automat, garantând izolarea completă a scenariilor de test. În combinație cu biblioteca android-arch-core-testing, dezvoltatorul poate gestiona ciclul de viață al bazei și poate verifica corectitudinea migrărilor fără a fi nevoie să curețe starea manual.
Performanța Room depinde direct de structura interogărilor și a indexurilor. Pentru analiza interogărilor lente, Room oferă flagul enableQueryCallback, care înregistrează toate interogările SQL cu timpul de execuție. Dezvoltatorul poate folosi acest jurnal pentru a găsi interogările care durează mai mult de 100 de milisecunde și a le optimiza prin adăugarea de indecși compuși prin adnotarea @Index în @Entity sau rescrierea subinterogărilor în JOIN-uri directe folosind @Relation.
Room suportă și criptarea bazei de date prin SQLCipher. Conectarea bibliotecii net.zetetic:android-database-sqlcipher și utilizarea SupportFactory în locul celei standard asigură criptarea transparentă a tuturor datelor pe disc fără modificarea interogărilor DAO și a structurii Entity. Acest lucru este necesar pentru aplicațiile care lucrează cu date personale ale utilizatorilor și îndeplinește cerințele GDPR și ale legii ruse 152-FZ privind protecția datelor personale. Parola de criptare poate fi stocată în Android Keystore pentru protecție împotriva extragerii prin instrumente pe dispozitive rootate.
Room suportă nativ Kotlin Coroutines începând cu versiunea 2.1. Metodele DAO pot fi funcții suspend care execută interogări în fundal fără a bloca firul principal. Room gestionează automat dispatcher-ele, folosind Dispatchers.IO pentru interogările de citire și scriere.
Pentru interogări reactive, Room returnează Flow — un flux rece de date care emite o nouă valoare la fiecare modificare a tabelului afectat. ViewModel se abonează la Flow prin stateIn sau collect, asigurând actualizarea automată a UI fără notificarea manuală a adaptorului.
Room suportă și Paging 3 printr-o implementare specială PagingSource care încarcă datele paginat din SQLite. Acest lucru este eficient pentru liste mari cu mii de înregistrări: Paging 3 încarcă doar rândurile vizibile pe ecran și le actualizează automat la modificări în baza de date.
Folosiți Paging 3 cu Room la afișarea fluxului de știri, a jurnalului de operații sau a listei de produse cu posibilitatea de acces offline și derulare infinită.
Întrebări frecvente
Room automatizează crearea tabelelor, conversia Cursor în obiecte și verificarea SQL la compilare. SQLiteOpenHelper necesită scrierea manuală a schemei, procesarea Cursor și nu verifică interogările înainte de lansarea aplicației, ceea ce crește riscul de erori.
Da, la modificarea Entity (adăugare/ștergere câmp, schimbare tip) este necesară migrarea. Fără ea, Room aruncă IllegalStateException la pornire. Pentru dezvoltare se poate activa fallbackToDestructiveMigration, dar în versiunea de producție sunt obligatorii scripturi corecte de migrare.
Room suportă @ForeignKey pentru operații în cascadă și @Relation pentru obiecte imbricate. Pentru interogări JOIN complexe se folosește adnotarea @Transaction cu @Query care returnează POJO cu entități imbricate prin @Embedded și @Relation.
Da, Room este complet compatibil cu Java. În locul funcțiilor suspend se folosesc LiveData sau RxJava Observable, în loc de Flow — LiveData. Room cu Java suportă aceleași adnotări, dar necesită mai mult cod boilerplate pentru operații asincrone.
Room suportă criptarea prin SQLCipher de la Zetetic. În loc de Room.databaseBuilder folosiți SupportFactory din biblioteca net.zetetic:android-database-sqlcipher, transmițând parola de criptare. Toate datele pe disc vor fi criptate transparent pentru interogările DAO.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și