Arquitetura de app
O CrossFly não impõe arquitetura e não tem DI nem binding reativo (ver Estado e fluxo de dados). Isso é liberdade — e responsabilidade. Abaixo, convenções sugeridas (compatíveis, não obrigatórias) que escalam. É o recorte de tarefa do Capítulo 32.
Estrutura de pacotes sugerida
app/
├─ ui/screens/ → uma tela = uma subclasse de Window
├─ viewmodel/ → estado + lógica de apresentação por tela
├─ data/repository → fachada sobre dev.crossfly.sqlite / rede
└─ service/ → serviços de app (singletons) + service locator
Uma tela = uma Window
public class ClientesScreen extends Window {
private final ClientesVM vm = new ClientesVM(Services.get(ClienteRepository.class));
@Override public void onShow() throws Exception {
super.onShow(); // monte a UI aqui (bounds válidos)
// … liga UI ↔ vm …
vm.observe(this::render); // re-renderiza quando o estado muda
vm.carregar();
}
@Override public void onRemove() { vm.dispose(); super.onRemove(); } // libere recursos
}
ViewModel leve (sem setState)
Como não há binding reativo, o ViewModel guarda estado e expõe um observe(Runnable); a View
re-renderiza manualmente:
public class ClientesVM {
private final ClienteRepository repo;
private Runnable onChange;
public List<Cliente> clientes = List.of();
public ClientesVM(ClienteRepository repo) { this.repo = repo; }
public void observe(Runnable r) { this.onChange = r; }
public void carregar() {
new Thread(() -> {
List<Cliente> dados = repo.listar();
Component.onUI(() -> { clientes = dados; if (onChange != null) onChange.run(); });
}).start();
}
public void dispose() { onChange = null; }
}
Repository sobre SQLite
Isole o acesso a dados numa fachada — a UI nunca fala SQL direto:
public class ClienteRepository {
public List<Cliente> listar() {
try (SqliteDb db = SqliteDb.open("app.db");
SqliteCursor c = db.query("SELECT id, nome FROM cliente")) {
List<Cliente> out = new ArrayList<>();
while (c.next()) out.add(new Cliente(c.getLong(0), c.getString(1)));
return out;
}
}
}
No lugar de DI: um service locator simples
A antiga Registry foi removida. Um Map<Class,Object> próprio resolve (ou injeção por construtor):
public final class Services {
private static final Map<Class<?>, Object> reg = new HashMap<>();
public static <T> void put(Class<T> k, T v) { reg.put(k, v); }
public static <T> T get(Class<T> k) { return k.cast(reg.get(k)); }
}
// no bootstrap: Services.put(ClienteRepository.class, new ClienteRepository());
Boas práticas
- UI em
onShow(), recursos liberados emonRemove(). - Estado na ViewModel; mutações voltam à paint thread via
onUI; a View traduz estado→componentes. - Repository esconde SQLite/rede; a tela não conhece SQL.
Erros comuns
- Procurar DI/observáveis do Flutter — não existem; use service locator +
observe(...)manual. - Colocar SQL/HTTP direto na
Window(acopla e trava a UI). - Guardar estado de app em campos da
Window(some ao navegar).
Veja também
- Conceito: Estado e fluxo de dados · Gerência de estado
Esta página foi útil?Obrigado pelo feedback!