# State management

> The CrossFly state pattern — no reactive binding, manual ViewModel + onUI + repaint.

Source: https://crossfly.dev/docs/data-and-services/state-management — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# State management

If you come from React/Flutter/Compose, brace yourself for the biggest difference: **there is no
reactive data-binding**. No `useState`/`Observable`/`setState()` that redraws on its own. The model is
**imperative + explicit invalidation** — the "why" is in
[State and data flow](/docs/concepts/state-and-data-flow).

## The pattern
State lives in **plain fields** of an object of yours (a ViewModel/service). When it changes, **you update
the widget** and call `repaint()` — if it came from another thread, inside `Component.onUI(...)`.
```java
class HomeVM {
   List<Cliente> clientes = new ArrayList<>();
}

public class TelaHome extends Window {
   private final HomeVM vm = new HomeVM();
   private ListView<Cliente> lista;

   @Override public void onShow() throws Exception {
      super.onShow();
      setLayout(new VerticalLayout());
      add(LAY_FILL, lista = new ListView<>());
      carregar();
   }

   private void carregar() {
      new Thread(() -> {
         List<Cliente> dados = repositorio.buscarTudo();   // heavy work, background thread
         Component.onUI(() -> {                             // back to the paint thread
            vm.clientes = dados;
            lista.setItems(dados);                          // updates the widget
            Component.repaint();                            // requests a new frame
         });
      }).start();
   }
}
```

## Ephemeral vs. application state
| Type | Where it lives | Survives navigation? |
|---|---|---|
| **ephemeral (screen)** | `Window` fields | no — gone when you leave the screen |
| **application** | a shared singleton/service | yes — persists across screens |

Choose by scope: a search filter is ephemeral; the logged-in user is application state.

## Best practices
- One **ViewModel per screen** concentrates state and logic; the `Window` only wires UI ↔ VM.
- State shared across screens → **service/singleton**, not scattered static fields.
- Always return to the UI with `onUI(...)` before touching widgets from a background thread.

## Common mistakes
- Expecting that changing the model updates the UI on its own — **it does not**; call `setX(...)` + `repaint()`.
- Storing app state in `Window` fields (lost when you navigate).

## See also
- Concept: [State and data flow](/docs/concepts/state-and-data-flow) · [The paint thread](/docs/concepts/the-paint-thread)
- [Persistence with SQLite](/docs/data-and-services/storage-sqlite) · [Background work](/docs/data-and-services/background-work)
- App architecture: [best-practices](/docs/best-practices)
