Pular para conteúdo

Aula 19 - Persistência Local com Room DB e Injeção Hilt 💉

Objetivo Pedagógico

Objetivo: Implementar persistência offline-first robusta com Room Database (SQLite), migrações de esquema e injeção de dependências modular com Hilt/Dagger em arquitetura limpa.


📑 1. Fundamentos Teóricos & Análise Técnica

Aplicativos móveis de qualidade industrial devem adotar uma postura Offline-First: o banco de dados local atua como a única fonte da verdade para a interface do usuário, enquanto as requisições de rede atuam como sincronizadores secundários em segundo plano.

O ecossistema Android Jetpack resolve essa complexidade através de: 1. Room Persistence Library: - Camada de abstração sobre o SQLite que provê verificação em tempo de compilação de instruções SQL, prevenindo erros de sintaxe em runtime. - Entidades (@Entity): Mapeamento direto de Data Classes Kotlin para tabelas relacionais com chaves primárias, índices compostos e restrições de unicidade. - Data Access Objects (@Dao): Métodos declarativos com retorno de tipos reativos como Flow<List<T>>, emitindo automaticamente novas listas sempre que dados da tabela sofrem inserção ou alteração. - Migrações Automáticas e Manuais: Evolução de esquema sem perda de dados do usuário através de objetos Migration declarativos. 2. Injeção de Dependências com Hilt (Dagger): - Framework oficial que elimina o boilerplate de instanciação manual de componentes. - Fornece contêineres de injeção amarrados aos ciclos de vida nativos do Android: @Singleton (aplicação), @ActivityRetainedScoped (ViewModel) e @ActivityScoped (Activity).

📐 Arquitetura Conceitual & Diagrama de Fluxo

graph TD
    App["Application Context (@HiltAndroidApp)"] --> Singleton["Módulo Hilt de Banco (@Singleton)"]
    Singleton --> RoomDB["Room Database Instance"]
    RoomDB --> Dao["OrderDao (@Dao)"]
    Dao --> Repo["OrdersRepositoryImpl (@Inject)"]
    Repo --> VM["OrdersViewModel (@HiltViewModel)"]
    VM --> UI["Compose Screen"]
    RoomDB -.->|Emite Flow Reativo| Dao
    Dao -.->|Flow<List<Order>>| Repo
    Repo -.->|Flow| VM
    style App fill:#e1f5fe,stroke:#01579b
    style RoomDB fill:#fff3e0,stroke:#e65100
    style Repo fill:#f3e5f5,stroke:#7b1fa2
    style VM fill:#e8f5e9,stroke:#2e7d32
    style UI fill:#e0f2f1,stroke:#00695c

🔍 Pilares e Diretrizes Técnicas

Nesta unidade, aprofundamos os seguintes conceitos fundamentais: - Verificação SQL em Tempo de Compilação: O Room analisa cada query anotada com @Query durante o build do Gradle, garantindo tipagem correta. - Arquitetura Offline-First Reativa: A UI consome o Flow emitido pelo DAO; quando o worker de rede salva novos registros, a tela atualiza-se sozinha. - Inversão de Dependências com Hilt: Desacoplamento de interfaces e implementações facilitando a substituição por fakes/mocks em testes unitários. - Migração Segura de Versão de Banco: Preservação da integridade estrutural das tabelas SQLite entre updates do app na Play Store.


🛠️ 2. Implementação Prática em Persistência Relacional Android e Injeção de Dependências

Abaixo está a implementação técnica de referência, estruturada com padrões de engenharia de software e foco em robustez:

// OrderDatabaseAndHilt.kt (Room DAO Reativo e Módulo de Injeção Hilt)
package com.portal.android.data

import androidx.room.*
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton

@Entity(tableName = "orders_table")
data class OrderEntity(
    @PrimaryKey val id: String,
    val title: String,
    val amount: Double,
    val createdAt: Long = System.currentTimeMillis()
)

@Dao
interface OrderDao {
    @Query("SELECT * FROM orders_table ORDER BY createdAt DESC")
    fun getAllOrdersStream(): Flow<List<OrderEntity>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertOrders(orders: List<OrderEntity>)

    @Query("DELETE FROM orders_table WHERE id = :orderId")
    suspend fun deleteOrder(orderId: String)
}

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
    @Provides
    @Singleton
    fun provideOrderDao(db: AppDatabase): OrderDao = db.orderDao()
}

💡 Análise Passo a Passo do Código

  1. Retorno em Flow<List<OrderEntity>>: Cria um canal de comunicação reativo direto do banco SQLite até a camada de apresentação Compose.
  2. Estratégia OnConflictStrategy.REPLACE: Garante idempotência em operações de sincronização em massa vindas de APIs externas.
  3. Injeção via SingletonComponent: Assegura que apenas uma única instância de conexão SQLite permaneça ativa no ciclo de vida do processo.

🎯 3. Próximos Passos & Sequência Didática