Exemplos de Código: Capítulo 13 — Persistência com Room Database

Especialização em Mobile Nativo com Android • Demonstrações Práticas Compiláveis


💻 Código de Demonstração: @Entity, @Dao, Consultas Reativas com Flow e RoomDatabase

O código abaixo demonstra a arquitetura oficial de persistência SQLite no Android com a biblioteca Jetpack Room:

  1. Modelagem de tabela com a anotação @Entity e chaves primárias automáticas.
  2. Contrato de operações CRUD assíncronas no @Dao com suporte nativo a Flow reativo (notifica a UI automaticamente a cada mutação de dados).
  3. Configuração de RoomDatabase e padrão Repository para isolamento de dados.
package br.com.portal.mobile.android.cap13

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking

// =====================================================================
// 1. Entidade de Banco de Dados (@Entity)
// =====================================================================
/*
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Delete
import androidx.room.Database
import androidx.room.RoomDatabase
*/

// @Entity(tableName = "tarefas")
data class TarefaEntity(
    // @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val titulo: String,
    val descricao: String,
    val concluida: Boolean = false,
    val criadaEm: Long = System.currentTimeMillis()
)

// =====================================================================
// 2. Data Access Object (@Dao)
// =====================================================================

// @Dao
interface TarefaDao {
    // @Query("SELECT * FROM tarefas ORDER BY criadaEm DESC")
    fun listarTodas(): Flow<List<TarefaEntity>>

    // @Query("SELECT * FROM tarefas WHERE id = :id LIMIT 1")
    suspend fun buscarPorId(id: Long): TarefaEntity?

    // @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun inserir(tarefa: TarefaEntity): Long

    // @Query("UPDATE tarefas SET concluida = :status WHERE id = :id")
    suspend fun atualizarStatus(id: Long, status: Boolean)

    // @Delete
    suspend fun remover(tarefa: TarefaEntity)
}

// =====================================================================
// 3. Definição do Banco de Dados (@Database)
// =====================================================================
/*
@Database(entities = [TarefaEntity::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
    abstract fun tarefaDao(): TarefaDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "portal_tarefas.db"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}
*/

// =====================================================================
// 4. Repositório Corporativo Desacoplado
// =====================================================================

class TarefasRepository(private val dao: TarefaDao) {
    val todasTarefas: Flow<List<TarefaEntity>> = dao.listarTodas()

    suspend fun cadastrarTarefa(titulo: String, descricao: String) {
        val nova = TarefaEntity(titulo = titulo, descricao = descricao)
        dao.inserir(nova)
    }

    suspend fun alternarConclusao(id: Long, concluida: Boolean) {
        dao.atualizarStatus(id, concluida)
    }
}

// =====================================================================
// 5. Simulação de Teste em Memória
// =====================================================================

class FakeTarefaDao : TarefaDao {
    private val tabela = mutableListOf<TarefaEntity>()

    override fun listarTodas(): Flow<List<TarefaEntity>> = flowOf(tabela.toList())

    override suspend fun buscarPorId(id: Long): TarefaEntity? = tabela.find { it.id == id }

    override suspend fun inserir(tarefa: TarefaEntity): Long {
        val gerado = (tabela.size + 1).toLong()
        val salva = tarefa.copy(id = gerado)
        tabela.add(salva)
        return gerado
    }

    override suspend fun atualizarStatus(id: Long, status: Boolean) {
        val idx = tabela.indexOfFirst { it.id == id }
        if (idx != -1) {
            tabela[idx] = tabela[idx].copy(concluida = status)
        }
    }

    override suspend fun remover(tarefa: TarefaEntity) {
        tabela.removeIf { it.id == tarefa.id }
    }
}

fun main() = runBlocking {
    println("=== PERSISTÊNCIA LOCAL COM ROOM DATABASE ===")
    val fakeDao = FakeTarefaDao()
    val repo = TarefasRepository(fakeDao)

    repo.cadastrarTarefa("Finalizar Curso Android", "Implementar 20 capítulos no Padrão Ouro")
    repo.cadastrarTarefa("Estudar Jetpack Compose", "Praticar Modifiers e Room")

    repo.todasTarefas.collect { lista ->
        println("Tarefas persistidas no banco local (${lista.size} registros):")
        lista.forEach {
            val check = if (it.concluida) "[X]" else "[ ]"
            println(" -> $check ID ${it.id}: ${it.titulo} (${it.descricao})")
        }
    }
}