📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Android Nativo. Recomendado revisar antes de começar.

🏛️ P19: Banco de Wakanda (MVVM, Room Database & Segurança Financeira)

Bem-vindo à oitava etapa da Fase 7: Arquitetura Avançada & Persistência Reativa! 🏛️

Neste projeto prático, você aprenderá a implementar a arquitetura de software recomendada pela Google (MVVM - Model-View-ViewModel) combinada com persistência local avançada via Room Database, injeção de dependências e o padrão Offline-First (Single Source of Truth).

Com a temática do Tesouro Real e Banco Nacional de Wakanda (Vibranium Kryptos - VK), construiremos um sistema financeiro robusto com cartão digital Black Panther, extrato de transações alimentado por Kotlin Flows reativos, proteção rigorosa contra SQL Injection via consultas DAO parametrizadas e isolamento de banco de dados na sandbox do Android (em conformidade com o OWASP Mobile Security)!

Fluxo Completo de Telas do Banco de Wakanda


📱 Galeria de Telas da Aplicação

1. Banco Local Limpo 2. Sincronizado (Single Source) 3. Diálogo de Transferência
Tela 1 - Banco Vazio Tela 2 - Sincronizado Tela 3 - Diálogo
Estado inicial sem dados locais em cache. Saldo consolidado e extrato persistido no Room. Modal para registro seguro de nova movimentação.

✅ Pré-requisitos e Continuidade

Antes de iniciar este laboratório, certifique-se de ter compreendido:


🎯 Objetivos de Aprendizagem

Ao concluir este projeto autoguiado, você será capaz de:

  1. Estruturar um Aplicativo em Camadas (MVVM): Separar UI declarativa (Compose), lógica de negócio e estado (ViewModel) e acesso a dados (Repository / DAO).
  2. Implementar Persistência Local com Room: Mapear tabelas SQLite com @Entity, definir operações com @Dao e instanciar o @Database.
  3. Adotar o Padrão Single Source of Truth: Fazer a interface observar unicamente o banco de dados local via Kotlin Flow, garantindo que dados da nuvem sejam primeiro persistidos antes de serem exibidos.
  4. Prevenir Injeção de SQL (SQL Injection): Escrever consultas parametrizadas seguras no Room DAO.
  5. Gerenciar Estado Reativo na UI: Coletar dados do ViewModel no Compose com collectAsState() e emitir atualizações assíncronas em Coroutines.

🏗️ Fluxo de Dados Offline-First (Single Source of Truth)

graph TD
    A[Servidor / API WakandaBankApi] -->|Sincronização em Nuvem| B[WakandaRepository]
    B -->|dao.inserirTodas| C[(SQLite / Room Database)]
    C -->|Flow<List<Transacao>>| B
    B -->|todasTransacoes Flow| D[WakandaViewModel]
    D -->|StateFlow / collectAsState| E[Interface Jetpack Compose]
    E -->|Nova Transferência| D
    D -->|viewModelScope.launch| B
    B -->|dao.inserir| C

🛡️ Boas Práticas de Segurança em Dados Financeiros (OWASP Mobile M2/M9)

[!IMPORTANT] Blindagem de Dados e Prevenção de Vulnerabilidades Bancárias: Armazenamento inseguro de dados e injeção de comandos são vetores críticos em aplicações financeiras.

Nossa Estratégia de Defesa:

  1. Isolamento em Sandbox do App (Data Isolation): O banco de dados do Room reside no diretório privado da aplicação (/data/data/br.com.curso.wakanda/databases/), protegido por permissões de UID do Linux que impedem o acesso por outros aplicativos.
  2. Prevenção de SQL Injection: O compilador do Room (kapt 'androidx.room:room-compiler') analisa as queries em tempo de compilação e utiliza Prepared Statements parametrizados com :parametro, impedindo manipulação maliciosa de strings SQL.
  3. Integridade Criptográfica: Cada transação possui um identificador com assinatura hash simulada (SHA256:...) para auditoria de integridade.
  4. Higiene de Versionamento: Arquivo .gitignore configurado para evitar o vazamento de artefatos de compilação ou arquivos locais.

📖 Dicionário Técnico do Projeto

Termo / Componente O que é e para que serve?
MVVM Padrão arquitetural que divide o código em Model (Dados), View (Interface) e ViewModel (Estado e Regras de Negócio).
Room Database Biblioteca de abstração do Android sobre o SQLite que oferece persistência tipada e verificação de queries em tempo de compilação.
DAO (Data Access Object) Interface que declara os métodos de leitura e escrita (@Query, @Insert, @Delete) no banco de dados.
Single Source of Truth Prática em que o banco local é a única fonte primária de verdade para a interface do usuário.
Kotlin Flow Fluxo assíncrono e reativo de dados que emite atualizações em tempo real para os observadores da UI.
StateFlow Variável observável e com retenção de estado que notifica o Compose a cada mutação de dados.

🛠️ Passo a Passo de Implementação

🚀 Passo 1: Criando o Novo Projeto no Android Studio

  1. Abra o Android Studio e clique em New Project (ou File > New > New Project).
  2. Selecione o template Empty Activity (com o ícone do Jetpack Compose 🌌).
  3. Preencha as configurações do projeto:
    • Name: Banco de Wakanda (ou android_p19_banco_mvvm_hilt)
    • Package name: br.com.curso.wakanda
    • Save location: Pasta do seu projeto no repositório
    • Language: Kotlin
    • Minimum SDK: API 24 ("Nougat"; Android 7.0) ou superior
    • Build configuration language: Groovy DSL (build.gradle) ou Kotlin DSL (build.gradle.kts)
  4. Clique em Finish e aguarde o Gradle sincronizar.

📦 Passo 2: Configurando Dependências no Gradle (build.gradle)

Abra app > build.gradle:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'org.jetbrains.kotlin.kapt'
}

dependencies {
    implementation platform('androidx.compose:compose-bom:2024.02.00')
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    implementation 'androidx.compose.material3:material3'
    implementation 'androidx.activity:activity-compose:1.8.2'
    implementation 'androidx.core:core-ktx:1.12.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'

    // Room Database
    implementation 'androidx.room:room-runtime:2.6.1'
    implementation 'androidx.room:room-ktx:2.6.1'
    kapt 'androidx.room:room-compiler:2.6.1'
}

📄 Passo 3: Manifesto do Aplicativo (AndroidManifest.xml)

Abra app > src > main > AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:label="Banco de Wakanda"
        android:supportsRtl="true"
        android:theme="@style/Theme.CartaoTreinador">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

🏛️ Passo 4: Entidade e DAO Seguro do Room

Abra app > src > main > java > br > com > curso > wakanda > MainActivity.kt:

@Entity(tableName = "transacoes_wakanda")
data class TransacaoVibranium(
    @PrimaryKey val id: String,
    val titulo: String,
    val valorVK: Double,
    val dataHora: String,
    val tipo: String, // "ENTRADA" ou "SAIDA"
    val hashSeguranca: String
)

@Dao
interface TransacaoDao {
    // Retorna Flow reativo monitorado pelo Room
    @Query("SELECT * FROM transacoes_wakanda ORDER BY id DESC")
    fun listarTodas(): Flow<List<TransacaoVibranium>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun inserir(transacao: TransacaoVibranium)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun inserirTodas(transacoes: List<TransacaoVibranium>)
}

🔄 Passo 5: Repositório e ViewModel MVVM

class WakandaRepository(private val dao: TransacaoDao) {
    val todasTransacoes: Flow<List<TransacaoVibranium>> = dao.listarTodas()

    suspend fun sincronizarComApi() {
        delay(1000) // Simulação de latência de rede segura
        val dadosNuvem = WakandaBankApi.buscarTransacoesNuvem()
        dao.inserirTodas(dadosNuvem)
    }

    suspend fun registrarTransacao(transacao: TransacaoVibranium) {
        dao.inserir(transacao)
    }
}

class WakandaViewModel(private val repository: WakandaRepository) : ViewModel() {
    val transacoes = repository.todasTransacoes

    private val _estaSincronizando = MutableStateFlow(false)
    val estaSincronizando: StateFlow<Boolean> = _estaSincronizando.asStateFlow()

    fun sincronizar() {
        viewModelScope.launch {
            _estaSincronizando.value = true
            repository.sincronizarComApi()
            _estaSincronizando.value = false
        }
    }
}

💳 Passo 6: Interface do Cartão Black Panther no Compose

@Composable
fun WakandaScreen() {
    val context = LocalContext.current
    val database = remember { Room.inMemoryDatabaseBuilder(context, WakandaDatabase::class.java).build() }
    val repository = remember { WakandaRepository(database.transacaoDao()) }
    val viewModel = remember { WakandaViewModel(repository) }

    val transacoesList by viewModel.transacoes.collectAsState(initial = emptyList())
    val estaSincronizando by viewModel.estaSincronizando.collectAsState()

    val saldoTotal = transacoesList.sumOf { if (it.tipo == "ENTRADA") it.valorVK else -it.valorVK }

    // Renderização do Cartão Black Panther de Vibranium com saldo formatado em VK
}

🔍 Guia de Diagnóstico & Resolução de Problemas (Troubleshooting)

Sintoma Observado Causa Provável Como Resolver
Cannot access database on the main thread Uma operação de escrita ou leitura síncrona do Room foi chamada na thread principal. Use suspend fun no DAO e invoque as chamadas dentro de viewModelScope.launch { ... } ou retorne um Flow.
A lista da UI não atualiza após chamar inserir() O DAO está retornando List<T> simples em vez de Flow<List<T>>. Altere o tipo de retorno do método de consulta no DAO para Flow<List<TransacaoVibranium>>.
IllegalStateException: Room cannot verify the data integrity Alteração na estrutura da @Entity sem incremento de versão ou fallbackToDestructiveMigration(). Incremente a version no @Database ou configure as migrações automáticas do Room.
Erro de compilação com KAPT O plugin org.jetbrains.kotlin.kapt não foi declarado no build.gradle. Adicione id 'org.jetbrains.kotlin.kapt' no bloco de plugins do módulo app.

🏆 Desafios e Upgrades (Mão na Massa!)

  1. 🔍 Filtro de Extrato por Categoria: Adicione chips para filtrar as transações no Room por apenas “Entradas (+)” ou “Saídas (-)”.
  2. 🔐 Criptografia do Banco com SQLCipher: Integre o net.zetetic:android-database-sqlcipher para criptografar o arquivo físico do SQLite com chave AES de 256 bits.
  3. 📈 Gráfico de Evolução de Saldo: Combine os conhecimentos do projeto P18 (Canvas 2D) para renderizar um gráfico de linha do histórico de saldo de Wakanda!

📖 Gabarito Oficial de Código (Para Conferência)

Disponível em: app/src/main/java/br/com/curso/wakanda/MainActivity.kt.


🚀 Como Executar no Laboratório

1. Abra o terminal na pasta deste projeto

No seu editor/IDE, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:

cd proj_aplicacoes_full_stack/projetos/android_p19_banco_mvvm_hilt

2. Execute a aplicação e os testes

./gradlew build
./gradlew test
# ou abrir no Android Studio e clicar em Run (Shift+F10)

[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code ou Android Studio, abra o projeto diretamente pela pasta proj_aplicacoes_full_stack/projetos/android_p19_banco_mvvm_hilt.