Pular para o conteúdo
CrossFly é 100% open sourceSiga no GitHub
CrossFlyCrossFly
Começar

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 em onRemove().
  • 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

Esta página foi útil?