7. Gerenciando estado
O que você vai aprender: o modelo de estado do CrossFly — onde quem vem de Flutter/React precisa desaprender. Pré-requisitos: Lidando com entrada.
Exemplo completo (copiar, colar e rodar)
Uma ViewModel leve (TarefaVM) com observe(Runnable) manual (não há binding reativo): a View re-popula
os componentes quando o estado muda. Um botão adiciona tarefas e a lista se re-renderiza. Compila como está.
package com.exemplo;
import java.util.*;
import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
public class MainWindow extends Window
{
private final TarefaVM vm = new TarefaVM();
private final Component listaBox = new Component();
private int contador = 1;
@Override
public void onShow() throws Exception
{
super.onShow();
setLayout(new VerticalLayout());
Button add = new Button("Adicionar tarefa");
add.onAction(c -> vm.adicionar(new Tarefa("Tarefa " + (contador++))));
add(LAY_AUTO, add);
listaBox.setLayout(new VerticalLayout());
add(LAY_FILL, listaBox);
vm.observe(this::renderizarLista); // re-popula quando o estado muda
renderizarLista();
}
private void renderizarLista()
{
listaBox.removeAll();
for (Tarefa t : vm.tarefas) listaBox.add(LAY_AUTO, new Label(t.titulo));
listaBox.setValidLayout(false); // o tamanho mudou
Component.repaint();
}
}
class Tarefa
{
final String titulo;
Tarefa(String titulo) { this.titulo = titulo; }
}
class TarefaVM
{
private Runnable onChange;
public List<Tarefa> tarefas = new ArrayList<>();
public void observe(Runnable r) { this.onChange = r; }
private void mudou() { if (onChange != null) onChange.run(); }
public void adicionar(Tarefa t)
{
var novo = new ArrayList<>(tarefas); novo.add(t);
tarefas = novo;
mudou(); // notifica a View (na paint thread)
}
}
Os trechos abaixo mostram só o miolo (o que vai dentro de
onShow()). Para rodar, coloque-os no esqueletoMainWindowacima.
Não há binding reativo
Mudar o modelo não atualiza a UI sozinho. O estado vive em campos (ou numa ViewModel leve), e a
View traduz estado → componentes manualmente, chamando repaint() (e setValidLayout(false) se o
tamanho mudou).
Uma ViewModel leve com observe
public class TarefaVM {
private Runnable onChange;
public List<Tarefa> tarefas = List.of();
public void observe(Runnable r) { this.onChange = r; }
private void mudou() { if (onChange != null) onChange.run(); }
public void adicionar(Tarefa t) {
var novo = new ArrayList<>(tarefas); novo.add(t);
tarefas = novo;
mudou(); // notifica a View (na paint thread)
}
}
A View reage
@Override public void onShow() throws Exception {
super.onShow();
// … monta UI …
vm.observe(this::renderizarLista); // re-popula os componentes quando o estado muda
vm.carregar(); // dispara o carregamento (em background — próximo capítulo)
}
private void renderizarLista() {
listaBox.removeAll();
for (Tarefa t : vm.tarefas) { CardTarefa card = new CardTarefa(); listaBox.add(LAY_AUTO, card); card.bind(t); }
listaBox.setValidLayout(false);
Component.repaint();
}
Regra: mutações e notificações sempre na paint thread. Se o dado veio de fundo, entre em
Component.onUI(...)antes de mexer no estado que a View lê (ver próximo capítulo).
Constrói neste passo
A TarefaVM com a tela se re-renderizando quando o estado muda.
Recapitulando
Sem reativo: estado em campos/ViewModel; a View traduz estado→componentes com repaint(); um
observe(Runnable) manual substitui o binding.
Próximos passos
Veja também
- Conceito: Estado e fluxo de dados · Arquitetura de app