📚 Pré-requisitos Teóricos: este projeto consome os serviços da Parte 01 — Spring Boot API e a infraestrutura conteinerizada da Parte 05 — Docker + PostgreSQL.
Seja muito bem-vindo à Parte 08 da série de projetos Lista de Tarefas!
Nesta etapa prática, você aprenderá a dominar a terceira grande trilha de equivalência entre desenvolvimento Web e Mobile Híbrido / Cross-platform:
\[\text{Web SPA (Angular 20 Standalone)} \longrightarrow \text{Mobile Híbrido Cross-Platform (Ionic Framework 8 + Capacitor)}\]💡 “Use um framework completo com arquitetura definida.”
Enquanto no Angular tradicional para desktop desenvolvemos interfaces baseadas em tabelas, formulários convencionais e navegação em tela cheia, o ecossistema móvel exige uma experiência tátil rica: gestos de deslize (swipe actions), puxar para atualizar (pull-to-refresh), diálogos nativos modais e notificações toast. O Ionic Framework sobre o Capacitor permite que toda a elegância da arquitetura Angular (TypeScript, injeção de dependências e programação reativa com RxJS) seja transferida diretamente para o smartphone, gerando pacotes nativos para Android (APK/AAB) e iOS (IPA) com uma única base de código.
Tanto o aplicativo Web SPA em Angular quanto o cliente móvel híbrido em Ionic operam consumindo a nossa Fonte Única da Verdade (SSOT): a API REST Spring Boot na porta 8080, conectada ao banco de dados relacional e padronizada com o modelo canônico { id, descricao, concluida }.
NgModule e simplificando rotas e injeção com provideIonicAngular().ion-item-sliding, ion-refresher, ion-checkbox, ion-badge, ion-spinner).cleartext: true para homologação local.No desenvolvimento mobile híbrido com Capacitor, a resolução de endereço de rede depende do ambiente de execução do aplicativo:
http://localhost:8080/api/tarefas.localhost refere-se ao próprio dispositivo virtual. O Capacitor detecta a plataforma via Capacitor.isNativePlatform() e redireciona automaticamente para o gateway da máquina hospedeira:
\(\text{URL no Emulador Android} = \mathbf{http://10.0.2.2:8080/api/tarefas}\)http://192.168.1.100:8080/api/tarefas).| ID | Descrição | Endpoint REST |
|---|---|---|
| RF-I01 | Listar tarefas ordenadas com pull-to-refresh (ion-refresher) e filtros de exibição. |
GET /api/tarefas |
| RF-I02 | Criar nova tarefa com validação de preenchimento obrigatório da descrição. | POST /api/tarefas |
| RF-I03 | Alternar status concluída/pendente via endpoint patch atômico ou atualização put. | PATCH /api/tarefas/{id}/status |
| RF-I04 | Modificar a descrição da tarefa através de AlertController interativo. |
PUT /api/tarefas/{id} |
| RF-I05 | Deletar permanentemente uma tarefa com confirmação prévia e feedback por toast. | DELETE /api/tarefas/{id} |
| ID | Categoria | Descrição |
|---|---|---|
| RNF-I01 | Arquitetura Standalone | O frontend e o mobile devem utilizar Angular Standalone Components sem dependência de NgModule. |
| RNF-I02 | Ergonomia Móvel | A interface do cliente Ionic deve responder a gestos táteis nativos (ion-item-sliding, ion-refresher). |
| RNF-I03 | Resolução Inteligente de Host | O serviço TarefaService deve alternar dinamicamente entre localhost:8080 e 10.0.2.2:8080 conforme a plataforma nativa. |
| RNF-I04 | Feedback Assíncrono | Todas as operações de criação, erro ou exclusão devem emitir mensagens flutuantes via ToastController. |
| RNF-I05 | Segurança de Rede (Cleartext) | A configuração do Capacitor Android deve liberar conexões HTTP em desenvolvimento através de cleartext: true. |
flowchart LR
User(("👤 Usuário Web / Mobile"))
subgraph "Aplicação Angular / Ionic"
UC01["UC01: Listar Tarefas com Pull-to-Refresh"]
UC02["UC02: Inserir Nova Tarefa"]
UC03["UC03: Alternar Status via Checkbox"]
UC04["UC04: Editar Tarefa com Swipe Action"]
UC05["UC05: Excluir Tarefa com Swipe Action"]
end
User --> UC01
User --> UC02
User --> UC03
User --> UC04
User --> UC05
tarefaService.listar().ion-refresher exibe o indicador visual circular.event.target.complete().POST /api/tarefas com { "descricao": "...", "concluida": false }.ToastController confirma a ação.ion-checkbox).PATCH /api/tarefas/{id}/status.AlertController nativo com campo de texto.PUT /api/tarefas/{id} persiste o novo texto na API.DELETE /api/tarefas/{id} e exibe-se um toast informativo.| Caso de Uso | História de Usuário | Requisito Funcional | Requisito Não-Funcional | Arquivo Responsável |
|---|---|---|---|---|
| UC01 | US-I01 | RF-I01 | RNF-I01, RNF-I02, RNF-I03 | tarefas.page.ts, tarefas.page.html |
| UC02 | US-I02 | RF-I02 | RNF-I01, RNF-I04 | tarefas.page.ts, tarefa.service.ts |
| UC03 | US-I03 | RF-I03 | RNF-I01, RNF-I03 | tarefas.page.ts, ion-checkbox |
| UC04 | US-I04 | RF-I04 | RNF-I01, RNF-I02 | tarefas.page.ts, AlertController |
| UC05 | US-I05 | RF-I05 | RNF-I01, RNF-I04 | tarefas.page.ts, ion-item-sliding |
O projeto está dividido entre a aplicação Web SPA tradicional e o cliente móvel híbrido:
fullstack_tarefas_08_spring_angular/
├── assets/
│ ├── arquitetura_angular_ionic_ecosystem.svg ← Diagrama conceitual Angular para Ionic
│ └── fluxo_gestos_ionic_api.svg ← Diagrama de gestos móveis e REST
├── web-angular/ ← Web SPA Standalone (Angular 20)
│ ├── src/
│ │ ├── app/
│ │ │ ├── components/
│ │ │ │ ├── tarefa-form/ ← Formulário Web de criação/edição
│ │ │ │ └── tarefa-list/ ← Tabela responsiva de listagem
│ │ │ ├── models/tarefa.model.ts ← Interface { id, descricao, concluida }
│ │ │ └── services/tarefa.service.ts ← Cliente HttpClient (Porta 8080)
│ │ └── main.ts ← Bootstrap Angular Standalone
│ └── package.json
├── mobile-ionic/ ← Cliente Mobile Híbrido (Ionic 8 + Capacitor)
│ ├── capacitor.config.json ← Configuração com cleartext: true
│ ├── ionic.config.json ← Integração Ionic CLI
│ ├── package.json ← Dependências Ionic, Angular e Capacitor
│ └── src/
│ ├── app/
│ │ ├── models/tarefa.model.ts ← Modelo unificado com a API central
│ │ ├── pages/tarefas/
│ │ │ ├── tarefas.page.ts ← Lógica mobile, gestos e diálogos
│ │ │ ├── tarefas.page.html ← Template com ion-item-sliding e refresher
│ │ │ └── tarefas.page.scss ← Estilização mobile refinada
│ │ ├── services/tarefa.service.ts ← Detecção de host (10.0.2.2 vs localhost)
│ │ ├── app.component.ts ← Container ion-app e router-outlet
│ │ └── app.routes.ts ← Roteamento do app
│ ├── theme/variables.css ← Paleta de cores do Ionic
│ └── main.ts ← provideIonicAngular e provideHttpClient
└── index.md ← Guia Didático Padrão Ouro Educacional
mobile-ionic/src/app/services/tarefa.service.ts)O serviço detecta em tempo de execução a plataforma nativa do Capacitor:
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Capacitor } from '@capacitor/core';
import { Tarefa } from '../models/tarefa.model';
@Injectable({
providedIn: 'root'
})
export class TarefaService {
private http = inject(HttpClient);
private get baseUrl(): string {
const isNative = Capacitor.isNativePlatform();
const platform = Capacitor.getPlatform();
if (isNative && platform === 'android') {
return 'http://10.0.2.2:8080/api/tarefas';
}
return 'http://localhost:8080/api/tarefas';
}
listar(): Observable<Tarefa[]> {
return this.http.get<Tarefa[]>(this.baseUrl);
}
criar(tarefa: Tarefa): Observable<Tarefa> {
return this.http.post<Tarefa>(this.baseUrl, tarefa);
}
atualizar(id: number, tarefa: Tarefa): Observable<Tarefa> {
return this.http.put<Tarefa>(`${this.baseUrl}/${id}`, tarefa);
}
alternarStatus(id: number, concluida: boolean): Observable<Tarefa> {
return this.http.patch<Tarefa>(`${this.baseUrl}/${id}/status`, { concluida });
}
excluir(id: number): Observable<void> {
return this.http.delete<void>(`${this.baseUrl}/${id}`);
}
}
tarefas.page.html)A experiência móvel tira proveito do componente ion-item-sliding:
<ion-list lines="full">
<ion-item-sliding *ngFor="let tarefa of tarefasFiltradas" #slidingItem>
<ion-item class="tarefa-item" [class.concluida]="tarefa.concluida">
<ion-checkbox
slot="start"
[checked]="tarefa.concluida"
(ionChange)="alternarStatus(tarefa)"
></ion-checkbox>
<ion-label>
<h2>{{ tarefa.descricao }}</h2>
<p>ID #{{ tarefa.id }} · Deslize para opções</p>
</ion-label>
<ion-badge slot="end" [color]="tarefa.concluida ? 'success' : 'warning'">
{{ tarefa.concluida ? 'Concluída' : 'Pendente' }}
</ion-badge>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="primary" (click)="abrirEdicao(tarefa, slidingItem)">
<ion-icon slot="icon-only" name="create-outline"></ion-icon>
</ion-item-option>
<ion-item-option color="danger" (click)="confirmarExclusao(tarefa, slidingItem)">
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
Abra um terminal na pasta do backend oficial:
cd catalogo_projetos/fullstack_tarefas_01_spring_api
./mvnw spring-boot:run
✅ Checkpoint: Acesse
http://localhost:8080/api/tarefasno navegador e certifique-se de receber a lista em formato JSON com as propriedadesid,descricaoeconcluida.
Em outro terminal:
cd catalogo_projetos/fullstack_tarefas_08_spring_angular/frontend
npm install
npm start
✅ Checkpoint: Abra
http://localhost:4200e teste o cadastro e a listagem das tarefas na interface Web.
Em um terceiro terminal:
cd catalogo_projetos/fullstack_tarefas_08_spring_angular/mobile-ionic
npm install
npm start
✅ Checkpoint: Acesse
http://localhost:4200(ou a porta atribuída pelo Ionic CLI), abra o Device Mode nas ferramentas do desenvolvedor (F12) e teste os gestos de arrasto e os componentes Ionic.
Com o Android Studio e o emulador instalados:
npm run build
npx cap sync android
npx cap open android
No Android Studio, clique em Run ‘app’ (ícone do play verde). O aplicativo inicializará no emulador conectando-se ao host via http://10.0.2.2:8080/api/tarefas.
| Checkpoint | Ação / Cenário de Teste | Critério de Aceite | Status |
|---|---|---|---|
| CP-01 | Iniciar a API Spring Boot central na porta 8080 | Resposta 200 OK com array JSON na rota /api/tarefas. |
PASS ✅ |
| CP-02 | Executar SPA Angular Standalone (localhost:4200) |
Injeção via inject(), renderização limpa e listagem de dados ativa. |
PASS ✅ |
| CP-03 | Cadastrar e gerenciar tarefas na Web Angular | Operações POST/PUT disparadas e refletidas sem recarregar tela. |
PASS ✅ |
| CP-04 | Executar cliente Ionic no modo Device (localhost:4200) |
Componentes móveis (ion-card, ion-badge) renderizados com visual nativo. |
PASS ✅ |
| CP-05 | Testar gesto de swipe no ion-item-sliding |
Ações de edição e exclusão reveladas de forma fluida ao deslizar o item. | PASS ✅ |
| CP-06 | Rodar app via Capacitor no Emulador Android (10.0.2.2) |
App conecta perfeitamente, sincroniza dados e respeita regras de rede. | PASS ✅ |
| Sintoma | Causa Mais Provável | Solução Recomendada |
|---|---|---|
ERR_CONNECTION_REFUSED no emulador |
O app tentou conectar em localhost:8080 ao invés de 10.0.2.2:8080. |
Verifique se Capacitor.isNativePlatform() está ativo no tarefa.service.ts. |
Cleartext HTTP traffic not permitted |
O Android 9+ bloqueia tráfego HTTP sem SSL por padrão. | Garanta "server": { "cleartext": true } no capacitor.config.json. |
| Erro de CORS no console do navegador | O backend não aceitou a origem do cliente Ionic/Angular. | Certifique-se de que a API Spring Boot contém @CrossOrigin("*") ou configuração de WebConfig. |
| Itens deslizantes não respondem | Falta da importação standalone do IonItemSliding e IonItemOptions. |
Verifique o array imports do componente TarefasPage. |
Com a entrega da Parte 08 (Angular ➔ Ionic Framework), consolidamos a trilha completa de arquitetura híbrida e cross-platform:
Avançamos a seguir para a modernização da engenharia desktop corporativa em Java: aprendendo como e por que o mercado migrou do Swing clássico para JavaFX moderno com FXML, folhas de estilo CSS, propriedades reativas e arquitetura MVC rigorosa!