📚 Pré-requisitos Teóricos: este projeto é a evolução direta da Parte 04 — Mobile Android Clássico (Java + XML Views) e consome os serviços da Parte 01 — Spring Boot API.

📱 Projeto Lista de Tarefas (Parte 10): Evolução Móvel — De Android Clássico para Kotlin + Jetpack Compose

Seja muito bem-vindo à Parte 10 do ecossistema de aplicações Lista de Tarefas!

Na Parte 04, você construiu um cliente móvel nativo utilizando o ecossistema clássico do Android: Java puro, layouts estáticos em XML, infladores de view com findViewById, o gerenciador de memória RecyclerView com seus adapters e viewholders verbosos, e callbacks assíncronos de rede. Aquele projeto cumpriu um papel pedagógico vital: compreender como funcionam as entranhas da plataforma Android desde seus primórdios.

Agora, na Parte 10, você dará o salto profissional definitivo na engenharia de software para dispositivos móveis: aprenderá como e por que a indústria migrou do modelo imperativo de Views XML para o paradigma declarativo e reativo do Jetpack Compose com Kotlin moderno.

\[\text{Android Clássico (Java + Views XML + RecyclerView)} \longrightarrow \text{Android Moderno (Kotlin + Jetpack Compose + UDF)}\]

💡 “Construa interfaces nativas modernas com funções declarativas puras em Kotlin e gerenciamento unidirecional de estado.”

Trilha Prática de Aplicações Full Stack (10 Partes Integradas):
├── Parte 01: API REST Central com Spring Boot + YARC + Thymeleaf (SSR)
├── Parte 02: Cliente Web Desacoplado com Svelte (JavaScript Puro)
├── Parte 03: Cliente Desktop Clássico com Java Swing (UI Imperativa)
├── Parte 04: Cliente Mobile Clássico com Android (Java + Views XML)
├── Parte 05: Arquitetura Corporativa, DevOps e Docker + PostgreSQL 16
├── Parte 06: Web ➔ Mobile Híbrido: Vue.js 3 ➔ Quasar Framework
├── Parte 07: Web ➔ Mobile Cross-Platform: React 19 ➔ React Native (Expo)
├── Parte 08: Web ➔ Mobile Híbrido: Angular Standalone ➔ Ionic Framework
├── Parte 09: Evolução Arquitetural Desktop: De Swing para JavaFX Moderno (MVC + CSS)
└── Parte 10: Evolução Arquitetural Mobile: De Java/XML para Kotlin + Jetpack Compose [ESTE PROJETO]

🏛️ 1. O Salto Arquitetural: Comparativo “Antes (Parte 04) vs. Agora (Parte 10)”

Compreender o contraponto entre o desenvolvimento clássico baseado em Views e o desenvolvimento contemporâneo com Compose é uma das competências mais exigidas em entrevistas técnicas de alto nível:

Contraponto Arquitetural Android Clássico vs Compose

Dimensão de Engenharia Android Clássico (Parte 04) Android Moderno com Compose (Parte 10) Benefício de Engenharia
Paradigma de UI Imperativo: Infla arquivos XML e manipula nós da árvore manualmente (findViewById, setText, setVisibility). Declarativo: A interface é descrita como uma função pura em Kotlin (@Composable fun TarefaScreen(...)). A UI reage ao estado. Elimina descompasso entre o estado real dos dados e os textos visíveis na tela.
Listagem de Itens RecyclerView com TarefaAdapter, ViewHolder, onCreateViewHolder, onBindViewHolder e DiffUtil (mais de 120 linhas). LazyColumn { items(tarefas, key = { it.id }) { TarefaCard(it) } } (menos de 10 linhas). Redução de mais de 85% do código boilerplate sem perder reciclagem de memória.
Linguagem & Tipagem Java 17/21: Verbosidade clássica, getters/setters e risco constante de exceções em tempo de execução (NullPointerException). Kotlin 2.0: Classes concisas (data class Tarefa), imutabilidade por padrão e Null-Safety validada em tempo de compilação. Código expressivo, limpo e à prova de travamentos por valores nulos.
Assincronismo de Rede Callbacks aninhados do Retrofit (call.enqueue(new Callback() { ... })) ou threads manuais com runOnUiThread. Kotlin Coroutines (suspend fun): O código assíncrono é escrito de forma sequencial com withContext(Dispatchers.IO). Código legível, sem “callback hell”, com cancelamento estruturado e livre de vazamento de memória.
Gerenciamento de Estado Estado fragmentado entre Activity, Adapter e XML; perda de dados em rotações de tela sem onSaveInstanceState. UDF (Unidirectional Data Flow): ViewModel sobrevive à rotação e expõe um StateFlow<TarefaUiState> imutável. Estado centralizado, previsível e facilmente testável com testes unitários JUnit puros.
Design System & Estilo styles.xml, themes.xml, seletores em drawables XML e color.xml. Material Design 3 em Kotlin: Tokens tipados (MaterialTheme.colorScheme, animações nativas com animateColorAsState). Estilização tipada com suporte instantâneo a Dark Mode e Dynamic Colors sem tocar em XML.

🗺️ 2. Arquitetura Reativa & Papel no Ecossistema (SSOT)

Assim como todos os demais clientes da série, o aplicativo Android Jetpack Compose conecta-se à nossa Fonte Única da Verdade (SSOT): a API REST Spring Boot rodando na porta 8080, padronizada sob o contrato canônico { id, descricao, concluida }.

Arquitetura Android Jetpack Compose e Spring Boot

flowchart TD
    subgraph Clientes ["📱 Clientes Nativos do Ecossistema"]
        C1["📱 Android Clássico (Parte 04)<br>Java + Views XML"]
        C2["📱 Android Moderno (Parte 10)<br>Kotlin + Jetpack Compose"]
    end

    subgraph Rede ["🌐 Ponte de Virtualização"]
        EMU["10.0.2.2:8080<br>(Gateway Emulador AVD)"]
        WIFI["192.168.x.x:8080<br>(Smartphone Físico Wi-Fi)"]
    end

    subgraph Backend ["⚙️ Fonte Única da Verdade (SSOT)"]
        API["🚀 Spring Boot REST API<br>fullstack_tarefas_01_spring_api<br>Porta 8080"]
        DB[("🗄️ Banco de Dados H2 / PostgreSQL")]
    end

    C1 --> EMU
    C2 --> EMU
    C2 --> WIFI
    EMU --> API
    WIFI --> API
    API --> DB

    style C2 fill:#eff6ff,stroke:#3b82f6,stroke-width:2px
    style API fill:#f0fdf4,stroke:#10b981,stroke-width:2px

O Desafio da Conectividade: Emulador (10.0.2.2) vs. Aparelho Físico


⚡ 3. O Ciclo Unidirecional de Dados (UDF) & Recomposição Cirúrgica

O Jetpack Compose adota estritamente o padrão Unidirectional Data Flow (UDF):

Ciclo Unidirecional de Dados UDF

  1. Os Eventos Sobem: A interface gráfica não executa lógica de negócio. Quando o usuário clica em um botão, o Composable apenas emite uma função lambda (onToggle(tarefa.id)).
  2. O ViewModel Processa: O TarefaViewModel intercepta o evento, dispara uma Coroutine no escopo viewModelScope.launch e aciona o repositório em segundo plano (Dispatchers.IO).
  3. A API Responde: O Retrofit executa a chamada HTTP assíncrona para o Spring Boot e recebe o JSON atualizado.
  4. O Estado Imutável Desce: O ViewModel atualiza seu MutableStateFlow interno emitindo uma nova instância de TarefaUiState.
  5. Recomposição Cirúrgica: O runtime do Compose detecta a alteração de estado e redesenha exclusivamente os nós afetados da árvore de UI. Por exemplo: se apenas uma tarefa teve o checkbox marcado, apenas o TarefaCard correspondente é recomposto; todo o restante da tela permanece intocado!

📐 4. Engenharia de Software & Especificação de Requisitos

4.1. Histórias de Usuário (User Stories)

4.2. Requisitos Funcionais (RF)

ID Nome do Requisito Descrição Detalhada Endpoint REST
RF-C01 Listagem em LazyColumn Consultar tarefas da API via Coroutines e renderizar lista reativa sem adapters. GET /api/tarefas
RF-C02 Cadastro via Modal Coletar texto no TarefaDialog, validar obrigatoriedade e despachar POST. POST /api/tarefas
RF-C03 Alternância Atômica Inverter booleano concluida com atualização otimista na UI e persistência no banco. PUT /api/tarefas/{id}
RF-C04 Edição de Descrição Carregar texto original no diálogo e disparar PUT com a nova descrição. PUT /api/tarefas/{id}
RF-C05 Exclusão de Tarefa Enviar DELETE /api/tarefas/{id} e remover o item correspondente do StateFlow. DELETE /api/tarefas/{id}

4.3. Requisitos Não-Funcionais (RNF)

ID Categoria Critério Técnico
RNF-C01 UI Declarativa Pura 100% da interface construída em Jetpack Compose, sem arquivos de layout XML (res/layout/).
RNF-C02 Concorrência com Coroutines Nenhuma requisição de rede pode rodar na Thread Principal (Main Dispatcher).
RNF-C03 Arquitetura MVVM & UDF A camada de visão deve ser completamente desacoplada da camada de dados via StateFlow.
RNF-C04 Design System Material 3 Utilizar componentes oficiais do Material 3 com suporte a temas claro e escuro.
RNF-C05 Segurança de Tráfego Local O manifesto deve liberar tráfego HTTP em texto claro para comunicação com o emulador.

4.4. Casos de Uso do Sistema

flowchart LR
    User(["👤 Usuário Móvel (Android AVD / Smartphone)"])

    subgraph ComposeApp ["📱 Cliente Android (Kotlin + Jetpack Compose)"]
        direction TB
        UC01["UC01: Visualizar & Filtrar Tarefas em LazyColumn"]
        UC02["UC02: Inserir Nova Tarefa via FAB"]
        UC03["UC03: Alternar Status de Conclusão"]
        UC04["UC04: Editar Tarefa em Diálogo Modal"]
        UC05["UC05: Excluir Tarefa Permanentemente"]
    end

    subgraph SpringAPI ["⚙️ API REST Spring Boot (Porta 8080)"]
        direction TB
        E1["GET /api/tarefas"]
        E2["POST /api/tarefas"]
        E3["PUT /api/tarefas/{id}"]
        E4["DELETE /api/tarefas/{id}"]
    end

    User --> UC01
    User --> UC02
    User --> UC03
    User --> UC04
    User --> UC05

    UC01 -.->|"suspend fun listar()"| E1
    UC02 -.->|"suspend fun criar()"| E2
    UC03 -.->|"suspend fun atualizar()"| E3
    UC04 -.->|"suspend fun atualizar()"| E3
    UC05 -.->|"suspend fun deletar()"| E4

Detalhamento dos Casos de Uso:


4.5. Matriz de Rastreabilidade de Requisitos (RTM)

História de Usuário Requisito Funcional Requisito Não-Funcional Caso de Uso Componente Compose / Kotlin
US-C01 (Visualização Declarativa) RF-C01 RNF-C01, RNF-C02, RNF-C03 UC01 TarefaScreen.kt, FiltroSegmentedControl.kt
US-C02 (Inclusão via FAB) RF-C02 RNF-C01, RNF-C04 UC02 TarefaDialog.kt, TarefaViewModel.kt
US-C03 (Alternância de Status) RF-C03 RNF-C02, RNF-C03 UC03 TarefaCard.kt, TarefaRepository.kt
US-C04 (Edição Direta) RF-C04 RNF-C01, RNF-C03 UC04 TarefaDialog.kt, TarefaApiService.kt
US-C05 (Exclusão Segura) RF-C05 RNF-C02, RNF-C05 UC05 TarefaCard.kt, TarefaViewModel.kt

📂 5. Estrutura de Arquivos e Código-Fonte da Aplicação

A pasta do cliente móvel mobile-android-compose/ possui a seguinte organização arquitetural:

catalogo_projetos/fullstack_tarefas_10_spring_compose/
├── assets/
│   ├── arquitetura_android_compose_ecosystem.svg
│   ├── comparativo_views_xml_vs_compose.svg
│   └── fluxo_unidirecional_udf_compose_api.svg
├── index.md
├── listadetarefas-painel.ps1
└── mobile-android-compose/
    ├── build.gradle.kts
    ├── settings.gradle.kts
    ├── README.md
    └── app/
        ├── build.gradle.kts
        └── src/main/
            ├── AndroidManifest.xml
            └── java/br/com/curso/listadetarefas/
                ├── MainActivity.kt
                ├── data/
                │   ├── model/Tarefa.kt
                │   ├── remote/
                │   │   ├── TarefaApiService.kt
                │   │   └── RetrofitClient.kt
                │   └── repository/TarefaRepository.kt
                └── ui/
                    ├── theme/
                    │   ├── Color.kt
                    │   ├── Type.kt
                    │   └── Theme.kt
                    ├── screens/
                    │   ├── TarefaUiState.kt
                    │   └── TarefaScreen.kt
                    ├── viewmodel/TarefaViewModel.kt
                    └── components/
                        ├── TarefaCard.kt
                        ├── TarefaDialog.kt
                        └── FiltroSegmentedControl.kt

5.1. O Ponto de Entrada: MainActivity.kt

Observe a concisão extrema: a Activity tradicional cheia de findViewById e setContentView(R.layout.activity_main) é substituída por um bloco declarativo com suporte nativo a bordas de tela modernas (Edge-to-Edge):

package br.com.curso.listadetarefas

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import br.com.curso.listadetarefas.ui.screens.TarefaScreen
import br.com.curso.listadetarefas.ui.theme.TarefasTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            TarefasTheme {
                TarefaScreen()
            }
        }
    }
}

5.2. O Modelo e o Serviço Retrofit com Coroutines

Modelo Canônico (Tarefa.kt):

package br.com.curso.listadetarefas.data.model

import com.google.gson.annotations.SerializedName

data class Tarefa(
    @SerializedName("id") val id: Long? = null,
    @SerializedName("descricao") val descricao: String,
    @SerializedName("concluida") val concluida: Boolean = false
)

Serviço Retrofit com Funções Suspensas (TarefaApiService.kt):

package br.com.curso.listadetarefas.data.remote

import br.com.curso.listadetarefas.data.model.Tarefa
import retrofit2.Response
import retrofit2.http.*

interface TarefaApiService {
    @GET("tarefas")
    suspend fun listar(): List<Tarefa>

    @POST("tarefas")
    suspend fun criar(@Body tarefa: Tarefa): Tarefa

    @PUT("tarefas/{id}")
    suspend fun atualizar(@Path("id") id: Long, @Body tarefa: Tarefa): Tarefa

    @DELETE("tarefas/{id}")
    suspend fun deletar(@Path("id") id: Long): Response<Unit>
}

5.3. O Gerenciador de Estado: TarefaViewModel.kt

O ViewModel orquestra todas as operações em segundo plano através do viewModelScope e expõe um único fluxo observável imutável (StateFlow):

class TarefaViewModel(
    private val repository: TarefaRepository = TarefaRepository()
) : ViewModel() {

    private val _uiState = MutableStateFlow(TarefaUiState(isLoading = true))
    val uiState: StateFlow<TarefaUiState> = _uiState.asStateFlow()

    init {
        carregarTarefas()
    }

    fun carregarTarefas() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, errorMessage = null) }
            repository.listarTarefas()
                .onSuccess { lista ->
                    _uiState.update {
                        it.copy(tarefas = lista.sortedByDescending { t -> t.id ?: 0L }, isLoading = false)
                    }
                }
                .onFailure { erro ->
                    _uiState.update {
                        it.copy(isLoading = false, errorMessage = "Falha ao conectar: ${erro.message}")
                    }
                }
        }
    }

    fun alternarStatus(tarefa: Tarefa) {
        val modificada = tarefa.copy(concluida = !tarefa.concluida)
        viewModelScope.launch {
            _uiState.update { estado ->
                estado.copy(tarefas = estado.tarefas.map { if (it.id == tarefa.id) modificada else it })
            }
            repository.atualizarTarefa(modificada).onFailure { carregarTarefas() }
        }
    }
}

5.4. A Tela Declarativa: TarefaScreen.kt

O componente substitui todas as Views e Adapters XML antigos utilizando o componente de lista moderno LazyColumn:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TarefaScreen(viewModel: TarefaViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    Scaffold(
        topBar = {
            TopAppBar(title = { Text("Tarefas Nativo (Compose)") })
        },
        floatingActionButton = {
            FloatingActionButton(onClick = { viewModel.abrirDialogNovo() }) {
                Icon(Icons.Default.Add, contentDescription = "Nova Tarefa")
            }
        }
    ) { paddingValues ->
        Column(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
            FiltroSegmentedControl(
                filtroSelecionado = uiState.filtro,
                onSelectFiltro = { viewModel.definirFiltro(it) },
                totalTodas = uiState.totalTarefas,
                totalPendentes = uiState.totalPendentes,
                totalConcluidas = uiState.totalConcluidas
            )

            LazyColumn(modifier = Modifier.fillMaxSize()) {
                items(
                    items = uiState.tarefasFiltradas,
                    key = { it.id ?: 0L }
                ) { tarefa ->
                    TarefaCard(
                        tarefa = tarefa,
                        onToggle = { viewModel.alternarStatus(tarefa) },
                        onEdit = { viewModel.abrirEdicao(tarefa) },
                        onDelete = { tarefa.id?.let { viewModel.excluirTarefa(it) } }
                    )
                }
            }
        }
    }
}

🧪 6. Roteiro de Homologação & Checkpoints de Validação

Para validar o funcionamento integrado do aplicativo Android com a API Spring Boot:

Passo 1: Subir o Backend Spring Boot (SSOT)

Abra um terminal na pasta da API e inicie o serviço:

cd catalogo_projetos/fullstack_tarefas_01_spring_api/api-spring
./mvnw spring-boot:run

Verifique se a API está ativa:

curl -i http://localhost:8080/api/tarefas

Passo 2: Executar o Aplicativo no Android Studio

  1. Abra a pasta mobile-android-compose/ no Android Studio.
  2. Inicie o emulador oficial do Android (API 26+).
  3. Execute o app clicando no botão Play (Run).

Matriz de Testes de Aceitação:

Checkpoint Ação Realizada Resultado Esperado Status
CP-01 Inicializar o app no Emulador Conexão imediata com 10.0.2.2:8080; tarefas cadastradas na Parte 01 são renderizadas. PASS ✅
CP-02 Tocar no botão flutuante + (FAB) Modal TarefaDialog surge na tela com campo de texto e foco automático. PASS ✅
CP-03 Digitar “Dominar Jetpack Compose” e salvar Requisição POST disparada; novo card surge no topo da lista sem recarregar a tela. PASS ✅
CP-04 Tocar no checkbox de uma tarefa Animação de fade acionada, texto recebe tachado e PUT /api/tarefas/{id} é confirmado. PASS ✅
CP-05 Tocar nos chips de filtro (“Pendentes” / “Concluídas”) A listagem filtra instantaneamente na memória e os contadores atualizam dinamicamente. PASS ✅
CP-06 Tocar no ícone de lixeira de um card Requisição DELETE /api/tarefas/{id} executada; card é removido do LazyColumn. PASS ✅

🏆 7. Conclusão da Trilha Completa de 10 Projetos

Com a conclusão da Parte 10, o ecossistema Lista de Tarefas atinge a plenitude de sua proposta pedagógica:

  1. Backend & DevOps: Uma Fonte Única da Verdade robusta em Spring Boot 3, com suporte tanto a banco em memória H2 quanto a PostgreSQL 16 em containers Docker automatizados (Parte 01 e Parte 05).
  2. O Quarteto Web Moderno:
    • Svelte (JavaScript Puro / Sem Virtual DOM — Parte 02)
    • Vue.js 3 (Progressivo — Parte 06)
    • React 19 (Componentes & Ecossistema — Parte 07)
    • Angular Standalone (Arquitetura Rigorosa com TypeScript — Parte 08)
  3. A Dualidade Desktop Clássico vs. Moderno:
    • Java Swing Clássico (Parte 03) ➔ JavaFX Moderno com FXML, CSS e MVC (Parte 09).
  4. A Tríade Mobile Híbrida & Multiplataforma:
    • Quasar Framework com Capacitor (Parte 06).
    • React Native com Expo (Parte 07).
    • Ionic Framework com Capacitor (Parte 08).
  5. A Dualidade Mobile Nativa Android:
    • Android Clássico em Java com Views XML e RecyclerView (Parte 04).
    • Android Moderno em Kotlin com UI Declarativa em Jetpack Compose, Coroutines e UDF (Parte 10).

Você agora possui um panorama completo, comparativo e prático de toda a engenharia de software contemporânea aplicada a sistemas corporativos distribuídos!