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.

9. Persisting data

What you will learn: actually writing to disk with SQLite, behind a repository. Prerequisites: Loading data.

Complete example (copy, paste and run)

A SQLite repository (facade) isolating the SQL, called off the paint thread in a Thread, and the result displayed via Component.onUI. 1-based bind, 0-based cursor, try-with-resources. Compiles as is.

package com.exemplo;

import java.util.*;
import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.sqlite.*;

public class MainWindow extends Window
{
   private final TarefaRepositorySQLite repo = new TarefaRepositorySQLite();
   private final Component listaBox = new Component();

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

      Button add = new Button("Inserir e recarregar");
      add.onAction(c -> new Thread(() -> {               // I/O off the paint thread
         repo.inserir("Tarefa " + System.currentTimeMillis());
         var dados = repo.listar();
         Component.onUI(() -> renderizar(dados));         // back to the paint thread
      }).start());
      add(LAY_AUTO, add);

      listaBox.setLayout(new VerticalLayout());
      add(LAY_FILL, listaBox);

      new Thread(() -> {                                  // initial load in background
         var dados = repo.listar();
         Component.onUI(() -> renderizar(dados));
      }).start();
   }

   private void renderizar(List<Tarefa> dados)
   {
      listaBox.removeAll();
      for (Tarefa t : dados) listaBox.add(LAY_AUTO, new Label(t.id + " · " + t.titulo));
      listaBox.setValidLayout(false);
      Component.repaint();
   }
}

class Tarefa
{
   final int id; final String titulo;
   Tarefa(int id, String titulo) { this.id = id; this.titulo = titulo; }
}

class TarefaRepositorySQLite
{
   private static final String DB = "tarefas.db";

   public TarefaRepositorySQLite()
   {
      try (SqliteDb db = SqliteDb.open(DB)) {
         db.exec("CREATE TABLE IF NOT EXISTS tarefa(id INTEGER PRIMARY KEY, titulo TEXT, feito INTEGER)");
      } catch (Exception e) { Debug.handle(e); }
   }

   public List<Tarefa> listar()
   {
      List<Tarefa> out = new ArrayList<>();
      try (SqliteDb db = SqliteDb.open(DB);
           SqliteCursor c = db.query("SELECT id, titulo FROM tarefa ORDER BY id")) {
         while (c.next())                                 // getters 0-based
            out.add(new Tarefa(c.getInt(0), c.getString(1)));
      } catch (Exception e) { Debug.handle(e); }
      return out;
   }

   public void inserir(String titulo)
   {
      try (SqliteDb db = SqliteDb.open(DB)) {
         db.execute("INSERT INTO tarefa(titulo, feito) VALUES(?, ?)", titulo, 0);   // bind 1-based
      } catch (Exception e) { Debug.handle(e); }
   }
}

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

The dev.crossfly.sqlite module

SqliteDb/SqliteStmt/SqliteCursor — all AutoCloseable (use try-with-resources) and run off the paint thread.

A repository (facade)

Isolate the SQL in a class; the UI never speaks SQL directly:

public class TarefaRepositorySQLite {
   private static final String DB = "tarefas.db";

   public TarefaRepositorySQLite() {
      try (SqliteDb db = SqliteDb.open(DB)) {
         db.exec("CREATE TABLE IF NOT EXISTS tarefa(id INTEGER PRIMARY KEY, titulo TEXT, feito INTEGER)");
      } catch (Exception e) { Debug.handle(e); }
   }

   public List<Tarefa> listar() {
      List<Tarefa> out = new ArrayList<>();
      try (SqliteDb db = SqliteDb.open(DB);
           SqliteCursor c = db.query("SELECT id, titulo FROM tarefa ORDER BY id")) {
         while (c.next())                                   // getters 0-based
            out.add(new Tarefa(c.getInt(0), c.getString(1)));
      } catch (Exception e) { Debug.handle(e); }
      return out;
   }

   public void inserir(String titulo) {
      try (SqliteDb db = SqliteDb.open(DB)) {
         db.execute("INSERT INTO tarefa(titulo, feito) VALUES(?, ?)", titulo, 0);   // bind 1-based
      } catch (Exception e) { Debug.handle(e); }
   }
}

⚠️ The two golden rules

Operation Base
parameter bind (?) 1-based
column getter (cursor) 0-based

For bulk writes, use transaction(...)/bulkInsert(...). For sensitive data, open with a key (SqliteDb.open(file, key), see Security).

What you build in this step

The TarefaRepositorySQLite — the app now survives restarts.

Recap

SQLite via a repository, try-with-resources, off the paint thread; 1-based bind, 0-based cursor; transactions/bulk for batches.

Next steps

→ Making it responsive

See also

Was this page helpful?