📚 Pré-requisitos: É altamente recomendado ter concluído a Parte 01 — API REST e a Parte 03 — Cliente Desktop com Java Swing. Ter a API Spring Boot em execução na porta 8080.

🚀 Projeto Lista de Tarefas (Parte 09): Evolução Arquitetural — De Swing para JavaFX Moderno

Bem-vindo à Parte 09 da Trilha Prática de Aplicações Full Stack!

Na Parte 03, você construiu um cliente desktop funcional utilizando a biblioteca clássica Java Swing. Aquele projeto cumpriu perfeitamente o papel de introduzir o consumo de APIs REST no desktop sem a sobrecarga de sistemas modulares.

Agora, na Parte 09, você dará o salto profissional definitivo no ecossistema desktop em Java: aprenderá como e por que a indústria migrou do Swing para o JavaFX moderno, adotando o padrão MVC (Model-View-Controller), interfaces declarativas em FXML, estilização com CSS e propriedades reativas (ObservableList).

Trilha de Evolução do Cliente Desktop:
├── Parte 03: Java Swing (Desktop Clássico)             ← UI Imperativa em código Java puro
└── Parte 09: JavaFX 21 Moderno (Evolução Arquitetural)  ← FXML Declarativo + CSS + MVC + Reatividade

🏛️ 1. O Salto Arquitetural: Comparativo “Antes (Swing) vs. Agora (JavaFX)”

Compreender a evolução entre Swing e JavaFX é uma das competências mais valorizadas na engenharia de software para sistemas legados e modernos em Java:

Contraponto Swing vs JavaFX Moderno

Dimensão Arquitetural Como era no Swing (Parte 03) Como é no JavaFX Moderno (Parte 09) Benefício de Engenharia
Construção da Interface Imperativa: Componentes instanciados e posicionados manualmente em código Java (panel.add(button, BorderLayout.EAST)). Declarativa (FXML): A tela é descrita em arquivo XML limpo (MainView.fxml), desacoplado da lógica de negócio. Permite edição visual com ferramentas WYSIWYG (Scene Builder) e trabalho paralelo entre designers e desenvolvedores.
Estilização Visual Código Java rígido (btn.setBackground(new Color(...))) ou Look & Feel global. CSS Nativo: Estilização via propriedades como -fx-base: #4CAF50; e regras de classes visuais. Separação total entre estética visual e lógica de comportamento (idêntico à Web).
Padrão Arquitetural Monolítico: a janela (JFrame) acumulava desenho de tela, tratamento de eventos e regras de apresentação. MVC Estrito: View (.fxml), Controller (@FXML Controller), Model (Tarefa) e Service (TarefaApiService). Alta manutenibilidade, testabilidade e baixo acoplamento.
Gerenciamento de Listas DefaultTableModel e tableModel.addRow(), exigindo repaints manuais. Data Binding & Reatividade: ObservableList<Tarefa> e PropertyValueFactory. Ao alterar a lista observável, a TableView atualiza sozinha. Elimina código repetitivo de sincronização de tela (boilerplate).
Sistema de Módulos (JPMS) Classpath clássico (Java 8 style), sem isolamento estrito de pacotes. Modular (Java 9+): Descrito no module-info.java, controlando explicitamente dependências e permissões de reflexão. Máxima segurança de encapsulamento e preparação para criação de instaladores nativos com jlink/jpackage.

📐 2. Diagrama da Arquitetura MVC do JavaFX

Arquitetura MVC JavaFX e Spring Boot

flowchart TD
    subgraph View ["🖼️ Camada de Visão (Declarativa)"]
        FXML["📄 MainView.fxml (Layout XML)"]
        CSS["🎨 CSS (Estilos Visuais)"]
        FXML --- CSS
    end

    subgraph ControllerLayer ["🧠 Camada de Controle"]
        Ctrl["⚙️ MainViewController.java<br>(Eventos @FXML e Data Binding)"]
        ObsList["📊 ObservableList&lt;Tarefa&gt;<br>(Lista Reativa)"]
        Ctrl --- ObsList
    end

    subgraph ModelLayer ["📦 Camada de Modelo"]
        Model["📋 Tarefa.java<br>(Propriedades JavaFX / DTO)"]
    end

    subgraph ServiceLayer ["🌐 Camada de Comunicação HTTP"]
        Service["🔌 TarefaApiService.java<br>(HttpClient + Jackson)"]
    end

    subgraph BackendCentral ["⚙️ API REST Spring Boot (Porta 8080) — SSOT"]
        API["🚀 http://localhost:8080/api/tarefas"]
    end

    FXML -->|Injeta componentes @FXML| Ctrl
    Ctrl -->|Manipula| Model
    Ctrl -->|Dispara operações assíncronas| Service
    Service <-->|JSON REST| API
    ObsList -->|Atualiza automaticamente| FXML

    style View fill:#f8fafc,stroke:#64748b,stroke-width:2px
    style ControllerLayer fill:#eff6ff,stroke:#3b82f6,stroke-width:2px
    style ModelLayer fill:#fef3c7,stroke:#f59e0b
    style ServiceLayer fill:#f0fdf4,stroke:#10b981
    style BackendCentral fill:#faf5ff,stroke:#8b5cf6,stroke-width:2px

📑 3. Engenharia de Software & Especificação de Requisitos

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

3.2. Requisitos Funcionais (RF)

ID Nome do Requisito Descrição Detalhada Endpoint REST
RF-FX01 Carga de Dados na Inicialização Consultar tarefas da API via TarefaApiService e popular a ObservableList<Tarefa>. GET /api/tarefas
RF-FX02 Inclusão de Nova Tarefa Validar campo de texto, despachar POST /api/tarefas e adicionar item à tabela. POST /api/tarefas
RF-FX03 Toggle de Conclusão Alternar valor booleano e enviar PUT /api/tarefas/{id} sincronizando o status. PUT /api/tarefas/{id}
RF-FX04 Remoção de Tarefa Confirmar ação e enviar DELETE /api/tarefas/{id}, removendo da lista observável. DELETE /api/tarefas/{id}
RF-FX05 Sincronização Manual Fornecer botão de recarga para buscar atualizações feitas por clientes Web ou Mobile. GET /api/tarefas

3.3. Requisitos Não-Funcionais (RNF)

ID Categoria Critério Técnico
RNF-FX01 Arquitetura MVC Estrita Desacoplamento total entre visão FXML, lógica de controle e serviços HTTP.
RNF-FX02 Thread Safety (JavaFX UI Thread) Todo retorno de rede assíncrono deve ser despachado via Platform.runLater().
RNF-FX03 Conformidade Modular (JPMS) O arquivo module-info.java deve declarar expressamente requires e opens.
RNF-FX04 Estilização com CSS Nativo 100% da identidade visual configurada via folha de estilo styles.css.
RNF-FX05 Serialização JSON Moderna Utilizar com.fasterxml.jackson.databind para conversão de DTOs.

3.4. Casos de Uso do Sistema

flowchart LR
    User(["👤 Operador Desktop (JavaFX)"])

    subgraph AppFX ["💻 Cliente Desktop JavaFX"]
        direction TB
        UC01["UC01: Consultar Tarefas na TableView"]
        UC02["UC02: Adicionar Tarefa via Formulário"]
        UC03["UC03: Alternar Status de Conclusão"]
        UC04["UC04: Excluir Tarefa Selecionada"]
        UC05["UC05: Sincronizar com Servidor"]
    end

    subgraph BackendAPI ["⚙️ Spring Boot API (Porta 8080)"]
        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 -.-> E1
    UC02 -.-> E2
    UC03 -.-> E3
    UC04 -.-> E4
    UC05 -.-> E1

3.5. Matriz de Rastreabilidade de Requisitos (RTM)

História de Usuário Requisito Funcional Requisito Não-Funcional Caso de Uso Componente JavaFX
US-FX01 (Listagem Reativa) RF-FX01 RNF-FX01, RNF-FX02 UC01 MainViewController.java, MainView.fxml
US-FX02 (Cadastro Direto) RF-FX02 RNF-FX01, RNF-FX05 UC02 MainViewController.java, TarefaApiService.java
US-FX03 (Conclusão Rápida) RF-FX03 RNF-FX02, RNF-FX05 UC03 MainViewController.java, TarefaApiService.java
US-FX04 (Exclusão Segura) RF-FX04 RNF-FX01, RNF-FX05 UC04 MainViewController.java
US-FX05 (Concorrência Segura) RF-FX05 RNF-FX02, RNF-FX03 UC05 TarefaApiService.java, Platform.runLater()

⚙️ 4. Construção do Cliente JavaFX Passo a Passo

📂 Passo 1: O Sistema de Módulos do Java (module-info.java)

No Java 17+, aplicações corporativas utilizam o JPMS (Java Platform Module System). O JavaFX exige declarar os módulos requeridos e abrir os pacotes para reflexão de bibliotecas externas (como o motor FXML e o Jackson Databind):

Arquivo: src/main/java/module-info.java

module br.com.curso.listadetarefas.desktop {
    requires javafx.controls;
    requires javafx.fxml;
    requires java.net.http;
    requires com.fasterxml.jackson.databind;

    // Permite que o JavaFX e o Jackson acessem classes e campos privados via reflexao
    opens br.com.curso.listadetarefas.desktop to javafx.fxml, com.fasterxml.jackson.databind;
    exports br.com.curso.listadetarefas.desktop;
}

💡 Por que o opens é obrigatório? O parser FXML precisa instanciar o MainViewController e injetar campos anotados com @FXML. Da mesma forma, o Jackson precisa inspecionar construtores e métodos de Tarefa.java. No sistema de módulos do Java, se você não declarar opens ... to ..., a JVM bloqueará o acesso por segurança e lançará um IllegalAccessException.


📦 Passo 2: Configuração do Maven (pom.xml)

O pom.xml gerencia as dependências do OpenJFX 21, do Jackson e o Maven Shade Plugin:

Arquivo: pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>br.com.curso</groupId>
    <artifactId>desktop-javafx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>desktop-javafx</name>
    <description>Cliente Desktop Moderno em JavaFX com FXML, CSS e MVC</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <javafx.version>17.0.6</javafx.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javafx.version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>${javafx.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.17.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
            </plugin>
            <!-- Plugin Shade para empacotamento com MainClass apontando para Launcher -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.5.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>br.com.curso.listadetarefas.desktop.Launcher</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

📝 Passo 3: O Modelo com Propriedades Reativas (Tarefa.java)

Diferente do POJO estático do Swing, no JavaFX utilizamos propriedades observáveis (BooleanProperty) para permitir que alterações de checkbox sejam refletidas automaticamente:

Arquivo: src/main/java/br/com/curso/listadetarefas/desktop/Tarefa.java

package br.com.curso.listadetarefas.desktop;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Tarefa {
    private Long id;
    private String descricao;
    private boolean concluida;

    // Propriedade observavel para controle de selecao na TableView
    private final BooleanProperty selecionada = new SimpleBooleanProperty(false);

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getDescricao() { return descricao; }
    public void setDescricao(String descricao) { this.descricao = descricao; }
    public boolean isConcluida() { return concluida; }
    public void setConcluida(boolean concluida) { this.concluida = concluida; }

    public boolean isSelecionada() { return selecionada.get(); }
    public BooleanProperty selecionadaProperty() { return selecionada; }
    public void setSelecionada(boolean selecionada) { this.selecionada.set(selecionada); }
}

🎨 Passo 4: A Interface Declarativa em FXML (MainView.fxml)

Em vez de instanciar botões e layouts em código Java, a tela é declarada no arquivo XML:

Arquivo: src/main/resources/br/com/curso/listadetarefas/desktop/MainView.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="br.com.curso.listadetarefas.desktop.MainViewController">
   <top>
      <HBox alignment="CENTER_LEFT" prefHeight="70.0" spacing="10.0" BorderPane.alignment="CENTER">
         <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </padding>
         <children>
            <Label text="Nova Tarefa:">
               <font>
                  <Font name="System Bold" size="14.0" />
               </font>
            </Label>
            <TextField fx:id="novaTarefaTextField" HBox.hgrow="ALWAYS" onAction="#adicionarTarefa" promptText="O que precisa ser feito?" />
            <Button onAction="#adicionarTarefa" style="-fx-base: #4CAF50;" text="Adicionar" textFill="WHITE">
               <font>
                  <Font name="System Bold" size="12.0" />
               </font>
            </Button>
            <Button onAction="#carregarTarefas" style="-fx-base: #2196F3;" text="🔄 Atualizar" textFill="WHITE">
               <font>
                  <Font name="System Bold" size="12.0" />
               </font>
            </Button>
         </children>
      </HBox>
   </top>
   <center>
      <TableView fx:id="tabelaTarefas" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <columns>
            <TableColumn fx:id="colunaSelecao" prefWidth="50.0" text="Sel." />
            <TableColumn fx:id="colunaConcluida" prefWidth="75.0" text="Status" />
            <TableColumn fx:id="colunaDescricao" prefWidth="450.0" text="Descrição" />
            <TableColumn fx:id="colunaAcoes" prefWidth="150.0" text="Ações" />
         </columns>
         <columnResizePolicy>
            <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
         </columnResizePolicy>
      </TableView>
   </center>
</BorderPane>

🧠 Passo 5: O Controlador MVC (MainViewController.java)

O controlador vincula a View XML aos dados e às ações do usuário através da anotação @FXML:

Arquivo: src/main/java/br/com/curso/listadetarefas/desktop/MainViewController.java

package br.com.curso.listadetarefas.desktop;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import java.net.URL;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;

public class MainViewController implements Initializable {

    @FXML private TextField novaTarefaTextField;
    @FXML private TableView<Tarefa> tabelaTarefas;
    @FXML private TableColumn<Tarefa, Boolean> colunaSelecao;
    @FXML private TableColumn<Tarefa, Boolean> colunaConcluida;
    @FXML private TableColumn<Tarefa, String> colunaDescricao;
    @FXML private TableColumn<Tarefa, Void> colunaAcoes;

    private final TarefaApiService apiService = new TarefaApiService();
    private final ObservableList<Tarefa> tarefasObservableList = FXCollections.observableArrayList();

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        configurarTabela();
        carregarTarefas();
    }

    private void configurarTabela() {
        colunaSelecao.setCellValueFactory(cellData -> cellData.getValue().selecionadaProperty());
        colunaSelecao.setCellFactory(CheckBoxTableCell.forTableColumn(colunaSelecao));
        colunaSelecao.setEditable(true);

        colunaConcluida.setCellValueFactory(new PropertyValueFactory<>("concluida"));
        colunaConcluida.setCellFactory(col -> new TableCell<>() {
            @Override
            protected void updateItem(Boolean concluida, boolean empty) {
                super.updateItem(concluida, empty);
                if (empty || concluida == null) {
                    setText(null);
                    setStyle("");
                } else {
                    setText(concluida ? "Concluída" : "Pendente");
                    setStyle(concluida ? "-fx-text-fill: #27ae60; -fx-font-weight: bold;" : "-fx-text-fill: #e67e22;");
                }
            }
        });

        colunaDescricao.setCellValueFactory(new PropertyValueFactory<>("descricao"));
        configurarColunaAcoes();

        tabelaTarefas.setItems(tarefasObservableList);
        tabelaTarefas.setEditable(true);
    }

    private void configurarColunaAcoes() {
        colunaAcoes.setCellFactory(param -> new TableCell<>() {
            private final Button btnAlternar = new Button("✔️");
            private final Button btnEditar = new Button("✏️");
            private final Button btnExcluir = new Button("🗑️");
            private final HBox pane = new HBox(5, btnAlternar, btnEditar, btnExcluir);

            {
                btnAlternar.setOnAction(e -> {
                    Tarefa t = getTableView().getItems().get(getIndex());
                    t.setConcluida(!t.isConcluida());
                    atualizarTarefa(t);
                });
                btnEditar.setOnAction(e -> {
                    Tarefa t = getTableView().getItems().get(getIndex());
                    abrirDialogEdicao(t);
                });
                btnExcluir.setOnAction(e -> {
                    Tarefa t = getTableView().getItems().get(getIndex());
                    confirmarExclusao(t);
                });
            }

            @Override
            protected void updateItem(Void item, boolean empty) {
                super.updateItem(item, empty);
                setGraphic(empty ? null : pane);
            }
        });
    }

    @FXML
    public void carregarTarefas() {
        new Thread(() -> {
            try {
                List<Tarefa> tarefas = apiService.listarTarefas();
                Platform.runLater(() -> tarefasObservableList.setAll(tarefas));
            } catch (Exception e) {
                Platform.runLater(() -> exibirAlerta("Erro de Rede", "Não foi possível carregar as tarefas da API."));
            }
        }).start();
    }

    @FXML
    private void adicionarTarefa() {
        String descricao = novaTarefaTextField.getText().trim();
        if (descricao.isEmpty()) return;

        Tarefa nova = new Tarefa();
        nova.setDescricao(descricao);
        nova.setConcluida(false);

        new Thread(() -> {
            apiService.adicionarTarefa(nova);
            Platform.runLater(() -> {
                novaTarefaTextField.clear();
                carregarTarefas();
            });
        }).start();
    }

    private void atualizarTarefa(Tarefa tarefa) {
        new Thread(() -> {
            apiService.atualizarTarefa(tarefa);
            Platform.runLater(this::carregarTarefas);
        }).start();
    }

    private void abrirDialogEdicao(Tarefa tarefa) {
        TextInputDialog dialog = new TextInputDialog(tarefa.getDescricao());
        dialog.setTitle("Editar Tarefa");
        dialog.setHeaderText("Editar descrição da tarefa #" + tarefa.getId());
        dialog.setContentText("Nova descrição:");

        dialog.showAndWait().ifPresent(novaDescricao -> {
            if (!novaDescricao.trim().isEmpty()) {
                tarefa.setDescricao(novaDescricao.trim());
                atualizarTarefa(tarefa);
            }
        });
    }

    private void confirmarExclusao(Tarefa tarefa) {
        Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
        alert.setTitle("Confirmar Exclusão");
        alert.setHeaderText("Excluir tarefa #" + tarefa.getId() + " (" + tarefa.getDescricao() + ")?");
        alert.setContentText("Esta ação removerá a tarefa de forma permanente.");

        Optional<ButtonType> result = alert.showAndWait();
        if (result.isPresent() && result.get() == ButtonType.OK) {
            new Thread(() -> {
                apiService.deletarTarefa(tarefa.getId());
                Platform.runLater(this::carregarTarefas);
            }).start();
        }
    }

    private void exibirAlerta(String titulo, String mensagem) {
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle(titulo);
        alert.setHeaderText(null);
        alert.setContentText(mensagem);
        alert.showAndWait();
    }
}

🚀 Passo 6: O Padrão Launcher (Launcher.java e MainApp.java)

No Java 11+, uma aplicação JavaFX empacotada em .jar executável não pode ter como classe principal uma subclasse direta de javafx.application.Application caso o runtime do JavaFX não esteja no bootclasspath da JVM. Criamos a classe Launcher.java para contornar essa restrição da plataforma:

Arquivo: src/main/java/br/com/curso/listadetarefas/desktop/Launcher.java

package br.com.curso.listadetarefas.desktop;

/**
 * Ponto de entrada padrão que contorna a checagem de módulos do JavaFX.
 * Delega a execução para a classe MainApp que herda de Application.
 */
public class Launcher {
    public static void main(String[] args) {
        MainApp.main(args);
    }
}

Arquivo: src/main/java/br/com/curso/listadetarefas/desktop/MainApp.java

package br.com.curso.listadetarefas.desktop;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MainApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/br/com/curso/listadetarefas/desktop/MainView.fxml"));
        Parent root = loader.load();

        primaryStage.setTitle("Lista de Tarefas — Cliente Desktop JavaFX Moderno");
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

🚀 5. Como Executar no Laboratório

Opção 1: Via Maven Wrapper

cd catalogo_projetos/fullstack_tarefas_09_spring_javafx/desktop-javafx
.\mvnw.cmd clean package
java -jar target/desktop-javafx-1.0-SNAPSHOT.jar

Opção 2: Pelo Painel Integrado PowerShell

Execute o script listadetarefas-painel.ps1 na pasta do projeto:

.\listadetarefas-painel.ps1

🧪 6. Matriz de Homologação & Testes de Aceitação

Checkpoint Ação Realizada Comando / Critério Resultado Esperado Status
CP-01 Inicializar a aplicação JavaFX java -jar desktop-javafx-1.0-SNAPSHOT.jar A janela carrega com estilo CSS e dados populados na TableView. PASS ✅
CP-02 Digitar nova tarefa e clicar em Adicionar Preencher campo e clicar Linha adicionada à ObservableList sem recarregar a tela; status 201 retornado. PASS ✅
CP-03 Selecionar linha e clicar em Concluir/Reabrir Toggle de status Chamada PUT confirmada; coluna de status reflete alteração com texto verde. PASS ✅
CP-04 Selecionar linha e clicar em Excluir Confirmar exclusão Chamada DELETE despachada; item removido da TableView instantaneamente. PASS ✅
CP-05 Clicar no botão Atualizar Sincronização manual A aplicação busca alterações feitas via Web ou Mobile sem travar a interface. PASS ✅

🎓 7. Conclusão & Próximos Passos da Trilha

Ao comparar os dois clientes desktop construídos ao longo da trilha:

  1. Parte 03 (Java Swing): Mostrou que é possível construir ferramentas desktop nativas com rapidez, simplicidade e zero burocracia modular, entendendo as bases da Event Dispatch Thread e chamadas HTTP.
  2. Parte 09 (JavaFX Moderno): Ensinou como arquitetar aplicações desktop de grande porte com desacoplamento visual (FXML), estilização corporativa (CSS), propriedades observáveis e conformidade com o sistema de módulos (JPMS) do Java contemporâneo.

👉 Próximo Passo: Parte 10 — Evolução Móvel: De Android Clássico para Kotlin + Jetpack Compose

Agora que você dominou a modernização da engenharia desktop no ecossistema Java, feche com chave de ouro a nossa esteira de 10 partes descobrindo como a indústria transformou o desenvolvimento nativo móvel: migrando do modelo clássico imperativo de Views XML da Parte 04 para o paradigma puramente declarativo e reativo em Kotlin + Jetpack Compose, Coroutines e Unidirectional Data Flow (UDF)!