SQLite assíncrono no Android com Room e Coroutines Flow

Nelson Glauber
Sep 19, 2019 · 3 min read
Source: https://www.h2ometrics.com/what-is-flow-2/

Em dezembro de 2018, na sua versão 2.1.0-alpha03, o Room começou a dar suporte as suspended functions, mostrando que está alinhado com o pensamento de utilizar coroutines para realização de operações assíncronas. Na versão 2.2.0-alpha02 do Room foi incluído o suporte ao recurso de flows.

Para utilizar é bem simples como será demonstrado nas seções seguintes.

Adicione as dependências

Adicione as dependências do Room no build.gradle lembrando de aplicar o plugin do kapt.

apply plugin: 'kotlin-kapt'

...
dependencies {
...
def coroutines_version = '1.3.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
def room_version = "2.2.0-rc01"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
}

As dependências da API de coroutines também foram listadas pois são necessárias.

Configurando as classes de persistência

Defina as entidades que serão persistidas no banco. Neste exemplo, será criada apenas a classe User como listado a seguir.

@Entity
data class User(
@PrimaryKey(autoGenerate = true) val uid: Int,
var firstName: String?,
var lastName: String?
)

Em seguida defina o DAO que realizará as operações no banco de dados.

@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): Flow<List<User>>

@Query("SELECT * FROM user WHERE uid = :id")
fun getUser(id: Long): Flow<User?>

@Insert
suspend fun insert(users: User): Long

@Delete
suspend fun delete(user: User): Int
}

Perceba que o retorno dos métodos que retornam um Flow não são estão marcados como suspend, pois o método collect (da interface Flow) é que será uma função suspensa que executará a query na thread de I/O e mandará os resultado pra UI Thread. Já os métodos que inserem e excluem são suspend e serão executados fora da UI thread utilizando a função withContext(Dispatchers.IO).

Outro detalhe é que o método getUser(Int) pode retornar nulo caso não exista um usuário com o ID passado como parâmetro.

Por fim, defina a classe de banco de dados onde são listadas as entidades que serão persistidas e os métodos que provêm instâncias do DAO.

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}

Acessando o banco de dados

Para acessar o banco de dados, inicialize uma instância da classe AppDatabase. Em seguida, obtenha a instância do UserDao utilizando a instância do banco de dados.

private val db: AppDatabase by lazy {
Room.databaseBuilder(
applicationContext,
AppDatabase::class.java,
"userDb"
).build()
}

private val dao: UserDao by lazy {
db.userDao()
}

Para iniciar o flow, de modo a obter a lista de usuários da tabela do banco, basta fazer como a seguir:

lifecycleScope.launch {
dao.getAll().collect { list ->
// Use sua lista aqui!
}
}

O código acima pode ser chamado no onCreate da activity sem problemas. O atributo lifecycleScope é da própria activity e está definida na biblioteca de Lifecycle. Caso não queira, basta utilizar um CoroutineScope tradicional.

implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha05"

Se você chamar esse código na sua activity ou view model, cada vez que um registro for inserido, alterado ou excluído, o lambda passado para a função collect será executado, similar ao que acontece com o LiveData ou Observable do Rx. E com essa lista, você pode por exemplo, atualizar o adapter da ListView/RecyclerView.

Para obter apenas um objeto User e ficar observando suas mudanças seria bem semelhante:

lifecycleScope.launch {
dao.getUser(0).collect { user ->
val fullName =
if (user != null) "${user?.firstName}, ${user?.lastName}"
else "Usuário não existe"
// faça algo...
}
}

O código acima seria útil, por exemplo, em um tela de detalhes do usuário.

Para inserir um objeto User, basta fazer como a seguir:

lifecycleScope.launch {
// lembrando que aqui é UI thread...
withContext(Dispatchers.IO) {
// aqui é I/O
dao.insert(User(0, "Nelson", "Glauber"))
}
// e aqui tb é UI thread
}

E finalmente, para excluir um usuário, basta passar o respectivo objeto User para o método delete.

lifecycleScope.launch {
withContext(Dispatchers.IO) {
dao.delete(user)
}
}

É isso! Fica cada vez mais simples utilizar Room com atualização automática dos dados. Desta vez utilizando a API de Flows de Coroutines.

4br4ç05,
nglauber

Nelson Glauber

Written by

Android Developer. Google Developer Expert in Android/Kotlin. Author of “Dominando o Android”.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade