Persistência de Dados
no SQLite com Room
+Nelson Glauber
@nglauber

www.nglauber.com.br
• Solução padrão existente desde a
primeira versão do Android
• Instruções SQL
• Dados organizados em tabelas
• Chave primária e estrangeira
• Índices
• Views
• Triggers
Tipos de dados no SQLite
• INTEGER
• REAL
• TEXT
• BLOB
SQLiteOpenHelper
class MeuHelper(ctx: Context) :
SQLiteOpenHelper(ctx, "meuBanco", null, 1) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("create table carro ("+
"_id integer primary key autoincrement, "+
"nome text not null, placa text not null, "+
"ano text not null)")
}
override fun onUpgrade(db: SQLiteDatabase,
oldVersion: Int, newVersion: Int) {
}
}
Inserindo um registro
val helper = MeuHelper(context)
val db = helper.writableDatabase
val values = ContentValues()
values.put("nome", "Fusca")
values.put("placa", "FUS0001")
values.put("ano", 2012)
db.insert("carro", null, values)
db.close()
Atualizando um registro
val db = helper.writableDatabase
val values = ContentValues()
values.put("nome", "Celta")
values.put("placa", "CEL1001")
values.put("ano", 2010)
db.update("carro", values,
"_id = ?", arrayOf("7"))
db.close()
Excluindo um registro
val db = helper.writableDatabase
db.delete("carro", "_id = ?", arrayOf("1"))
db.close()
Uma Query no banco…
val db = helper.readableDatabase
val cursor = db.rawQuery(
"select * from carro where nome like ?",
arrayOf("Ce%"))
while (cursor.moveToNext()) {
val id = cursor.getLong(0)
val nome = cursor.getString(1)
val placa = cursor.getString(
cursor.getColumnIndex("placa"))
Log.d("NGVL", "${id}/${nome} ${placa}")
}
cursor.close()
db.close()
Poderia pra ser mais
simples?
Um ORM talvez… 🤔
ORMs Populares
https://github.com/pardom/ollie
https://github.com/pardom/ActiveAndroid
http://greenrobot.org/greendao/
ORMs Populares
http://ormlite.com/sqlite_java_android_orm.shtml
https://github.com/Raizlabs/DBFlow
Era uma vez em 2015…
Framework Fireside Chat 

(Android Dev Summit 2015)
youtu.be/-VNfWh5UkfY?t=44m2s
😡
No Google I/O 2017…
Architecture Components -
Persistence and Offline
(Google I/O '17)

youtu.be/MfHsPGQ6bgE?
t=2m26s
Levanta a mão agora quem
quer um ORM para Android!
Antes de falar sobre o Room…
vamos falar sobre como
deveria ser um ORM ideal? 🤔
Antes do I/O17…
Talk com Ubiratan Soares
youtu.be/kSmysslUoG8?
t=58m37s
–Ubiratan Soares
“Qual o ORM que eu mais gosto? 

Nenhum! Todos são ruins!”
Um ORM deve…
• Usar anotações (não usar reflection)
• Usar abordagem de DAO para salvar dados
• Ter query builder, validar e auto-completar o SQL
• Não deve me forçar a herdar de base class
• Suporte a RX para acesso assíncrono
• Suporte a migração ser programático e não baseado em
convenções
Room API
buildscript {
ext.roomVersion = '1.0.0-alpha4'
...
}
allprojects {
repositories {
maven { url 'https://maven.google.com' }
mavenCentral()
// google() no Android 3.0
jcenter()
}
}
Dependências
Dependências
dependencies {
...
implementation
"android.arch.persistence.room:runtime:$roomVersion"
kapt
"android.arch.persistence.room:compiler:$roomVersion"
androidTestImplementation
"android.arch.persistence.room:testing:roomVersion"
}
import android.arch.persistence.room.*
@Entity(indices = arrayOf(Index("descr")))
data class Event(
@PrimaryKey (autoGenerate = true)
var id : Long = 0,
var name : String = "",
@ColumnInfo(name = "descr")
var description : String = ""
)
Entity
@Entity
• Podemos definir o nome da tabela usando a propriedade
tableName
• Podemos definir uma chave primária composta usando a
propriedade primaryKeys e passar a lista dos campos
DAO
import android.arch.persistence.room.*
@Dao
interface EventDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(event: Event): Long
@Update
fun update(event: Event): Int
@Delete
fun delete(vararg event: Event): Int
@Query("SELECT * FROM Event WHERE descr LIKE :arg0 ORDER BY descr")
fun eventsByDescription(description: String = "%"): List<Event>
@Query("SELECT * FROM Event WHERE id = :arg0")
fun eventById(id: Long): Event
}
Regras gerais do DAO
• O Insert/Delete/Update pode receber 1 ou vários objetos
• A Query pode retornar 1 ou vários objetos
• O conjunto de colunas retornadas deve ser igual às
propriedades do objeto a ser retornado
Database
import android.arch.persistence.room.Database
import android.arch.persistence.room.RoomDatabase
@Database(entities = arrayOf(Event::class), version = 1)
abstract class MyRoomDatabase : RoomDatabase() {
abstract fun eventDao() : EventDao
}
val db = Room.databaseBuilder(applicationContext,
MyRoomDatabase::class.java, "myDb")
.build()
val dao = db.eventDao()
val event = Event(0, "TDC-SP", "The Developers Conference São Paulo")
val id = dao.insert(event)
val event2 = dao.eventById(id)
Log.d("NGVL", "${event2.id} ${event2.name} - ${event2.description}")
val events = dao.eventsByDescription()
events.forEach {
Log.d("NGVL", "${it.id} ${it.name} - ${it.description}")
}
Tipos de dados
incompatíveis
@Entity(indices = arrayOf(Index("descr")))
data class Event(
@PrimaryKey (autoGenerate = true)
var id : Long = 0,
var name : String = "",
@ColumnInfo(name = "descr")
var description : String = "",
var date: Date = Date()
)
Type Converter
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(value) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time ?: 0
}
}
@Database(entities = arrayOf(Event::class), version = 1)
@TypeConverters(Converters::class)
abstract class MyRoomDatabase : RoomDatabase() {
abstract fun eventDao() : EventDao
}
Embedded
@Entity(indices = arrayOf(Index("descr")))
data class Event(
@PrimaryKey (autoGenerate = true)
var id : Long = 0,
var name : String = "",
@ColumnInfo(name = "descr")
var description : String = "",
var date: Date? = null,
@Embedded
var location: Address? = null
)
data class Address {
var street: String? = null
var state: String? = null
var city: String? = null
}
Foreign Keys
import android.arch.persistence.room.*
import android.arch.persistence.room.ForeignKey.CASCADE
@Entity(foreignKeys = arrayOf(
ForeignKey(entity = Event::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("eventId"),
onDelete = CASCADE)))
data class Track(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
var name: String = "",
var eventId: Long = 0
)
O “problema" das 

Foreign Keys
• Eu devo carregar os registros filhos?
• Quando os registros filhos devem ser carregados?
• Lazy loading é a melhor abordagem?
Foreign Keys
@Dao
interface TrackDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertTracks(vararg tracks: Track)
@Query("SELECT * FROM Track WHERE eventId = :arg0")
fun tracksByEvent(eventId: Long): List<Track>
}
@Entity(indices = arrayOf(Index("descr")))
data class Event(
...
@Ignore
var tracks: List<Track>? = null
)
val event = eventDao.eventById(id)
event.tracks = trackDao.tracksByEvent(id)
Smart Join
@Dao
interface EventDao {
...
@Query("SELECT E.*, (SELECT COUNT(*) FROM Track T WHERE T.eventId = E.id)" +
" as numTracks FROM Event E")
fun eventsWithTrackCount(): List<EventWithTrack>
}
class EventWithTrack : Event() {
var numTracks : Int = 0
}
@Entity(...)
open class Event(...)
Migrations
@Entity(indices = arrayOf(Index("descr")))
data class Event(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
var name: String = "",
@ColumnInfo(name = "descr")
var description: String = "",
var date: Date? = null,
var capacity: Int = 0,
@Embedded
var location: Address? = null,
@Ignore
var tracks: List<Track>? = null
)
Migrations
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE Event ADD COLUMN capacity INTEGER NULL")
}
}
val db = Room.databaseBuilder(applicationContext,
MyRoomDatabase::class.java, "myDb")
.addMigrations(Migration_1_2)
.build()
Room + RX2
dependencies {
...
implementation "android.arch.persistence.room:rxjava2:+"
implementation "io.reactivex.rxjava2:rxjava:+"
implementation "io.reactivex.rxjava2:rxandroid:+"
}
Room + RX2
@Dao
interface EventDao {
...
@Query("SELECT * FROM Event WHERE descr LIKE :arg0 ORDER BY descr")
fun eventsByDescription(description: String = "%"): Flowable<List<Event>>
@Query("SELECT * FROM Event WHERE id = :arg0")
fun eventById(id: Long): Flowable<Event>
}
Room + RX2
dao.eventsByDescription()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { events ->
events.forEach {
Log.d("NGVL", "${it.id} ${it.name} - ${it.description}")
}
}
Testing
Testing
@RunWith(AndroidJUnit4::class)
class RoomDemoTests {
lateinit var db: MyRoomDatabase
@Before
@Throws(Exception::class)
fun initDb() {
db = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getContext(),
MyRoomDatabase::class.java)
//.allowMainThreadQueries() // please don't
.build()
}
Testing
@After
@Throws(Exception::class)
fun closeDb() {
db.close()
}
Conclusão…
✓Usa anotações ao invés de reflection
✓Usa abordagem de DAO para salvar dados
✓Tem que ter query builder, validar 

e auto-complete para o SQL
✓Não te força a herdar de uma base class
✓Suporta RX
✓Suporta migração programática
Parece bom
esse Room! *
* Essa não é necessariamente 

a opinião do Bira 🙃
Referências
• Room Persistence Library 

https://goo.gl/1z53tu
• Architecture Components do Android - Nelson Glauber

https://goo.gl/Wm7rvc
• 7 Steps To Room - Florina Muntenescu

https://goo.gl/jRDXid
• A evolução da persistência de dados (com sqlite) no
android - Rodrigo Castro

https://goo.gl/FCPDTR
Dúvidas?
@nglauber
+NelsonGlauber
www.nglauber.com.br

Persistência de Dados no SQLite com Room

  • 1.
    Persistência de Dados noSQLite com Room +Nelson Glauber @nglauber
 www.nglauber.com.br
  • 2.
    • Solução padrãoexistente desde a primeira versão do Android • Instruções SQL • Dados organizados em tabelas • Chave primária e estrangeira • Índices • Views • Triggers
  • 3.
    Tipos de dadosno SQLite • INTEGER • REAL • TEXT • BLOB
  • 4.
    SQLiteOpenHelper class MeuHelper(ctx: Context): SQLiteOpenHelper(ctx, "meuBanco", null, 1) { override fun onCreate(db: SQLiteDatabase) { db.execSQL("create table carro ("+ "_id integer primary key autoincrement, "+ "nome text not null, placa text not null, "+ "ano text not null)") } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { } }
  • 5.
    Inserindo um registro valhelper = MeuHelper(context) val db = helper.writableDatabase val values = ContentValues() values.put("nome", "Fusca") values.put("placa", "FUS0001") values.put("ano", 2012) db.insert("carro", null, values) db.close()
  • 6.
    Atualizando um registro valdb = helper.writableDatabase val values = ContentValues() values.put("nome", "Celta") values.put("placa", "CEL1001") values.put("ano", 2010) db.update("carro", values, "_id = ?", arrayOf("7")) db.close()
  • 7.
    Excluindo um registro valdb = helper.writableDatabase db.delete("carro", "_id = ?", arrayOf("1")) db.close()
  • 8.
    Uma Query nobanco… val db = helper.readableDatabase val cursor = db.rawQuery( "select * from carro where nome like ?", arrayOf("Ce%")) while (cursor.moveToNext()) { val id = cursor.getLong(0) val nome = cursor.getString(1) val placa = cursor.getString( cursor.getColumnIndex("placa")) Log.d("NGVL", "${id}/${nome} ${placa}") } cursor.close() db.close()
  • 9.
    Poderia pra sermais simples? Um ORM talvez… 🤔
  • 10.
  • 11.
  • 12.
    Era uma vezem 2015… Framework Fireside Chat 
 (Android Dev Summit 2015) youtu.be/-VNfWh5UkfY?t=44m2s
  • 13.
  • 15.
    No Google I/O2017… Architecture Components - Persistence and Offline (Google I/O '17)
 youtu.be/MfHsPGQ6bgE? t=2m26s
  • 16.
    Levanta a mãoagora quem quer um ORM para Android!
  • 17.
    Antes de falarsobre o Room… vamos falar sobre como deveria ser um ORM ideal? 🤔
  • 18.
    Antes do I/O17… Talkcom Ubiratan Soares youtu.be/kSmysslUoG8? t=58m37s
  • 19.
    –Ubiratan Soares “Qual oORM que eu mais gosto? 
 Nenhum! Todos são ruins!”
  • 20.
    Um ORM deve… •Usar anotações (não usar reflection) • Usar abordagem de DAO para salvar dados • Ter query builder, validar e auto-completar o SQL • Não deve me forçar a herdar de base class • Suporte a RX para acesso assíncrono • Suporte a migração ser programático e não baseado em convenções
  • 21.
  • 22.
    buildscript { ext.roomVersion ='1.0.0-alpha4' ... } allprojects { repositories { maven { url 'https://maven.google.com' } mavenCentral() // google() no Android 3.0 jcenter() } } Dependências
  • 23.
  • 25.
    import android.arch.persistence.room.* @Entity(indices =arrayOf(Index("descr"))) data class Event( @PrimaryKey (autoGenerate = true) var id : Long = 0, var name : String = "", @ColumnInfo(name = "descr") var description : String = "" ) Entity
  • 26.
    @Entity • Podemos definiro nome da tabela usando a propriedade tableName • Podemos definir uma chave primária composta usando a propriedade primaryKeys e passar a lista dos campos
  • 27.
    DAO import android.arch.persistence.room.* @Dao interface EventDao{ @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(event: Event): Long @Update fun update(event: Event): Int @Delete fun delete(vararg event: Event): Int @Query("SELECT * FROM Event WHERE descr LIKE :arg0 ORDER BY descr") fun eventsByDescription(description: String = "%"): List<Event> @Query("SELECT * FROM Event WHERE id = :arg0") fun eventById(id: Long): Event }
  • 28.
    Regras gerais doDAO • O Insert/Delete/Update pode receber 1 ou vários objetos • A Query pode retornar 1 ou vários objetos • O conjunto de colunas retornadas deve ser igual às propriedades do objeto a ser retornado
  • 29.
    Database import android.arch.persistence.room.Database import android.arch.persistence.room.RoomDatabase @Database(entities= arrayOf(Event::class), version = 1) abstract class MyRoomDatabase : RoomDatabase() { abstract fun eventDao() : EventDao }
  • 30.
    val db =Room.databaseBuilder(applicationContext, MyRoomDatabase::class.java, "myDb") .build() val dao = db.eventDao() val event = Event(0, "TDC-SP", "The Developers Conference São Paulo") val id = dao.insert(event) val event2 = dao.eventById(id) Log.d("NGVL", "${event2.id} ${event2.name} - ${event2.description}") val events = dao.eventsByDescription() events.forEach { Log.d("NGVL", "${it.id} ${it.name} - ${it.description}") }
  • 31.
    Tipos de dados incompatíveis @Entity(indices= arrayOf(Index("descr"))) data class Event( @PrimaryKey (autoGenerate = true) var id : Long = 0, var name : String = "", @ColumnInfo(name = "descr") var description : String = "", var date: Date = Date() )
  • 32.
    Type Converter class Converters{ @TypeConverter fun fromTimestamp(value: Long?): Date? { return value?.let { Date(value) } } @TypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time ?: 0 } } @Database(entities = arrayOf(Event::class), version = 1) @TypeConverters(Converters::class) abstract class MyRoomDatabase : RoomDatabase() { abstract fun eventDao() : EventDao }
  • 33.
    Embedded @Entity(indices = arrayOf(Index("descr"))) dataclass Event( @PrimaryKey (autoGenerate = true) var id : Long = 0, var name : String = "", @ColumnInfo(name = "descr") var description : String = "", var date: Date? = null, @Embedded var location: Address? = null ) data class Address { var street: String? = null var state: String? = null var city: String? = null }
  • 34.
    Foreign Keys import android.arch.persistence.room.* importandroid.arch.persistence.room.ForeignKey.CASCADE @Entity(foreignKeys = arrayOf( ForeignKey(entity = Event::class, parentColumns = arrayOf("id"), childColumns = arrayOf("eventId"), onDelete = CASCADE))) data class Track( @PrimaryKey(autoGenerate = true) var id: Long = 0, var name: String = "", var eventId: Long = 0 )
  • 35.
    O “problema" das
 Foreign Keys • Eu devo carregar os registros filhos? • Quando os registros filhos devem ser carregados? • Lazy loading é a melhor abordagem?
  • 36.
    Foreign Keys @Dao interface TrackDao{ @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertTracks(vararg tracks: Track) @Query("SELECT * FROM Track WHERE eventId = :arg0") fun tracksByEvent(eventId: Long): List<Track> } @Entity(indices = arrayOf(Index("descr"))) data class Event( ... @Ignore var tracks: List<Track>? = null ) val event = eventDao.eventById(id) event.tracks = trackDao.tracksByEvent(id)
  • 37.
    Smart Join @Dao interface EventDao{ ... @Query("SELECT E.*, (SELECT COUNT(*) FROM Track T WHERE T.eventId = E.id)" + " as numTracks FROM Event E") fun eventsWithTrackCount(): List<EventWithTrack> } class EventWithTrack : Event() { var numTracks : Int = 0 } @Entity(...) open class Event(...)
  • 38.
    Migrations @Entity(indices = arrayOf(Index("descr"))) dataclass Event( @PrimaryKey(autoGenerate = true) var id: Long = 0, var name: String = "", @ColumnInfo(name = "descr") var description: String = "", var date: Date? = null, var capacity: Int = 0, @Embedded var location: Address? = null, @Ignore var tracks: List<Track>? = null )
  • 39.
    Migrations object Migration_1_2 :Migration(1, 2) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE Event ADD COLUMN capacity INTEGER NULL") } } val db = Room.databaseBuilder(applicationContext, MyRoomDatabase::class.java, "myDb") .addMigrations(Migration_1_2) .build()
  • 40.
    Room + RX2 dependencies{ ... implementation "android.arch.persistence.room:rxjava2:+" implementation "io.reactivex.rxjava2:rxjava:+" implementation "io.reactivex.rxjava2:rxandroid:+" }
  • 41.
    Room + RX2 @Dao interfaceEventDao { ... @Query("SELECT * FROM Event WHERE descr LIKE :arg0 ORDER BY descr") fun eventsByDescription(description: String = "%"): Flowable<List<Event>> @Query("SELECT * FROM Event WHERE id = :arg0") fun eventById(id: Long): Flowable<Event> }
  • 42.
    Room + RX2 dao.eventsByDescription() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe{ events -> events.forEach { Log.d("NGVL", "${it.id} ${it.name} - ${it.description}") } }
  • 43.
  • 44.
    Testing @RunWith(AndroidJUnit4::class) class RoomDemoTests { lateinitvar db: MyRoomDatabase @Before @Throws(Exception::class) fun initDb() { db = Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getContext(), MyRoomDatabase::class.java) //.allowMainThreadQueries() // please don't .build() }
  • 45.
  • 46.
    Conclusão… ✓Usa anotações aoinvés de reflection ✓Usa abordagem de DAO para salvar dados ✓Tem que ter query builder, validar 
 e auto-complete para o SQL ✓Não te força a herdar de uma base class ✓Suporta RX ✓Suporta migração programática Parece bom esse Room! * * Essa não é necessariamente 
 a opinião do Bira 🙃
  • 47.
    Referências • Room PersistenceLibrary 
 https://goo.gl/1z53tu • Architecture Components do Android - Nelson Glauber
 https://goo.gl/Wm7rvc • 7 Steps To Room - Florina Muntenescu
 https://goo.gl/jRDXid • A evolução da persistência de dados (com sqlite) no android - Rodrigo Castro
 https://goo.gl/FCPDTR
  • 48.
  • 49.