Skip to content
CrossFly is 100% open sourceFollow us on GitHub
CrossFlyCrossFly
Get started

This page was machine-translated from Portuguese and may contain errors. The Portuguese version is canonical.

Your first app: task list

Let’s build an app that opens, adds, navigates and persists — step by step, introducing each piece in the order it’s needed.

Complete example (copy, paste and run)

A lean, self-contained version of the essentials: a TextField + Button that adds items to a virtual list. Paste it into a MainWindow class and run with gradlew :lwjgl3:run.

package com.exemplo;

import java.util.ArrayList;
import java.util.List;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

public class MainWindow extends Window
{
   private final List<String> tarefas = new ArrayList<>();
   private TextField campo;
   private VirtualListView lista;

   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      // input row: field (absorbs the width) + button (natural size)
      Component entrada = new Component();
      entrada.setLayout(new HorizontalLayout());
      campo = new TextField().caption("Nova tarefa");
      Button add = new Button("Adicionar");
      entrada.add(LAY_FILL, campo);
      entrada.add(LAY_AUTO, add);
      add(LAY_AUTO, entrada);

      // virtual list: each cell is 3x the font height
      lista = new VirtualListView(Relative.ofFont(3f), new DataAdapter()
      {
         @Override public int getCount() { return tarefas.size(); }
         @Override public Component createView() { return new Label(""); }
         @Override public void bindView(Component view, int index)   // view first, then index
         {
            ((Label) view).setText(tarefas.get(index));   // direct cast
         }
      });
      add(LAY_FILL, lista);

      add.onAction(c -> adicionar());
   }

   private void adicionar()
   {
      String texto = campo.getText().trim();
      if (texto.isEmpty())
      {
         Hint.show("Digite uma tarefa", 1500);   // always 2 args
         return;
      }
      tarefas.add(texto);
      campo.clearText();
      lista.notifyDataChanged();   // re-measures and repaints the list
   }
}

The snippets below show only the core (what goes inside onShow()). To run them, place them in the MainWindow skeleton above.

1. The screen skeleton (VerticalLayout)

public class TarefasScreen extends Window {
   private final List<Tarefa> tarefas = new ArrayList<>();   // state in Window fields
   private Component listaBox;
   private TextField campo;

   @Override public void onShow() throws Exception {
      super.onShow();
      setLayout(new VerticalLayout());

      // input row (field + button)
      Component entrada = new Component();
      entrada.setLayout(new HorizontalLayout());
      campo = new TextField().caption("Nova tarefa");
      Button add = new Button("Adicionar");
      entrada.add(LAY_FILL, campo);       // field absorbs the width
      entrada.add(LAY_AUTO, add);         // button sized to its content
      add(LAY_AUTO, entrada);

      // list area, which absorbs the rest of the height
      listaBox = new Component();
      listaBox.setLayout(new VerticalLayout());
      add(LAY_FILL, listaBox);

      add.onAction(c -> adicionar());
      carregar();                          // populates from the database
   }
}

2. Add and reflect in the UI (no reactive binding)

private void adicionar() {
   String texto = campo.getText().trim();
   if (texto.isEmpty()) return;
   Tarefa t = new Tarefa(texto);
   tarefas.add(t);
   salvar(t);              // persists (step 4)
   campo.clearText();
   renderizarLista();      // you update the UI manually
}

private void renderizarLista() {
   listaBox.removeAll();
   for (Tarefa t : tarefas) {
      Button item = new Button(t.titulo);
      item.onAction(c -> new DetalheScreen(t).show());   // navigation (step 3)
      listaBox.add(LAY_AUTO, item);
   }
   listaBox.setValidLayout(false);   // re-measures the layout
   Component.repaint();              // requests a new frame
}

3. Push/pop navigation

public class DetalheScreen extends Window {
   private final Tarefa tarefa;
   public DetalheScreen(Tarefa t) { this.tarefa = t; }   // data via the constructor
   @Override public void onShow() throws Exception {
      super.onShow();
      setLayout(new VerticalLayout());
      add(LAY_AUTO, new Label(tarefa.titulo));
      Button voltar = new Button("Voltar");
      voltar.onAction(c -> hide());     // pop
      add(LAY_AUTO, voltar);
   }
}

4. Persistence in SQLite (off the paint thread)

private void carregar() {
   new Thread(() -> {
      List<Tarefa> carregadas = new ArrayList<>();
      try (SqliteDb db = SqliteDb.open("tarefas.db")) {
         db.exec("CREATE TABLE IF NOT EXISTS tarefa(id INTEGER PRIMARY KEY, titulo TEXT)");
         try (SqliteCursor c = db.query("SELECT id, titulo FROM tarefa ORDER BY id")) {
            while (c.next())                                   // getters 0-based
               carregadas.add(new Tarefa(c.getInt(0), c.getString(1)));
         }
      } catch (Exception e) { Debug.handle(e); }
      Component.onUI(() -> {                                   // back to the paint thread
         tarefas.clear(); tarefas.addAll(carregadas);
         renderizarLista();
      });
   }).start();
}

private void salvar(Tarefa t) {
   new Thread(() -> {
      try (SqliteDb db = SqliteDb.open("tarefas.db")) {
         db.execute("INSERT INTO tarefa(titulo) VALUES(?)", t.titulo);   // bind 1-based
      } catch (Exception e) { Debug.handle(e); }
   }).start();
}

Two golden rules of SQLite: parameter binding is 1-based, cursor getters are 0-based; and the database runs off the paint thread, with the result coming back via Component.onUI(...).

What you built

An app that opens, adds, navigates and persists — and you’ve already seen the pillars that the learn/ course goes deeper into.

Next steps

→ Anatomy of a project

See also

Was this page helpful?