Palestra sobre persistência de dados no SQLite com Room API apresentada no TDC São Paulo 2017 (19/07/2017). O Room é um dos Architectural Components apresentados pelo Google no Google I/O 2017.
2. • 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
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
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()
6. 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()
7. Excluindo um registro
val db = helper.writableDatabase
db.delete("carro", "_id = ?", arrayOf("1"))
db.close()
8. 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()
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
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 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
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 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
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")))
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
}
34. 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
)
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")))
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
)
41. 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>
}
46. 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 🙃
47. 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