This page was machine-translated from Portuguese and may contain errors. The Portuguese version is canonical.
7. Managing state
What you’ll learn: CrossFly’s state model — where people coming from Flutter/React need to unlearn. Prerequisites: Handling input.
Complete example (copy, paste, and run)
A lightweight ViewModel (TarefaVM) with a manual observe(Runnable) (there’s no reactive binding): the View re-populates
the components when the state changes. A button adds tasks and the list re-renders. Compiles as is.
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-populates when the state changes
renderizarLista();
}
private void renderizarLista()
{
listaBox.removeAll();
for (Tarefa t : vm.tarefas) listaBox.add(LAY_AUTO, new Label(t.titulo));
listaBox.setValidLayout(false); // the size changed
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(); // notifies the View (on the paint thread)
}
}
The snippets below show only the core (what goes inside
onShow()). To run them, put them in theMainWindowskeleton above.
There’s no reactive binding
Changing the model does not update the UI on its own. State lives in fields (or in a lightweight ViewModel), and the
View translates state → components manually, calling repaint() (and setValidLayout(false) if the
size changed).
A lightweight ViewModel with 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(); // notifies the View (on the paint thread)
}
}
The View reacts
@Override public void onShow() throws Exception {
super.onShow();
// … builds UI …
vm.observe(this::renderizarLista); // re-populates the components when the state changes
vm.carregar(); // triggers the load (in background — next chapter)
}
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();
}
Rule: mutations and notifications always on the paint thread. If the data came from the background, enter
Component.onUI(...)before touching the state the View reads (see the next chapter).
What you build in this step
The TarefaVM with the screen re-rendering when the state changes.
Recap
No reactive: state in fields/ViewModel; the View translates state→components with repaint(); a manual
observe(Runnable) replaces the binding.
Next steps
See also
- Concept: State and data flow · App architecture