Saltar al contenido
CrossFly es 100% open sourceSíguenos en GitHub
CrossFlyCrossFly
Empezar
Todos los ejemplos
RecetasDatos154 líneas · 1 archivos

Storing something

Add a few notes and reopen the recipe — they are still there. The same code runs on desktop, Android, iOS and the web.

Qué muestra esto

Storing something, on every platform, with no ORM and no generated code.

SqliteDb is the whole data layer — native on desktop, Android and iOS, and WASM on the web, so the same four calls work everywhere. exec for DDL, execute for a parameterised write, query for a cursor, and transaction to make several writes one thing.

Two habits worth copying from the first line you write. Bind parameters, never concatenate SQL — the ? form is not only safe, it lets SQLite reuse the prepared statement. And name your columns in the SELECT: the cursor reads by position, so a SELECT * silently shifts every getter the day somebody adds a column.

Ejecutarlo en tu máquina

Cada ejemplo es una Window normal, así que el lanzador solo necesita su nombre.

gradlew lwjgl3:run --args="classname dev.cf.recipe.R18Storage"
Editarlo

Java se compila antes de ejecutarse, así que el navegador no puede reconstruir una edición por su cuenta: el código que ves aquí se convirtió en JavaScript con TeaVM antes de publicarse. Copia el archivo a un proyecto y funciona sin cambios en las cuatro plataformas; el comando de arriba es toda la preparación.

Ejecutándose aquíCrossFly 0.1.32
Iniciando la aplicación…

Compilado a JavaScript por TeaVM: es el mismo código, no un vídeo de él. · Todos los ejemplos

Qué usa

Tipos del framework que toca este ejemplo. Los enlazados tienen página de referencia.

ButtonCardComponentDebugDeviceGapGridLayoutMaterialIconScreenSqliteCursorSqliteDbStylesTextFieldVerticalLayoutWidgets

El código

Completo, sin recortes. Es el archivo, no un fragmento.

Crudo
dev/cf/recipe/R18Storage.java
package dev.cf.recipe;

import dev.cf.Tones;

import dev.crossfly.device.Device;
import dev.crossfly.sqlite.SqliteCursor;
import dev.crossfly.sqlite.SqliteDb;
import dev.crossfly.ui.*;
import dev.crossfly.ui.color.*;
import dev.crossfly.ui.graphics.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.style.*;

import static dev.crossfly.ui.Widgets.*;

/**
 * Storing something, on every platform, with no ORM and no generated code.
 *
 * <p>{@link SqliteDb} is the whole data layer — native on desktop, Android and iOS, and WASM on the
 * web, so the same four calls work everywhere. {@code exec} for DDL, {@code execute} for a
 * parameterised write, {@code query} for a cursor, and {@code transaction} to make several writes
 * one thing.</p>
 *
 * <p>Two habits worth copying from the first line you write. <b>Bind parameters, never concatenate
 * SQL</b> — the {@code ?} form is not only safe, it lets SQLite reuse the prepared statement.
 * And <b>name your columns in the SELECT</b>: the cursor reads by position, so a {@code SELECT *}
 * silently shifts every getter the day somebody adds a column.</p>
 */
public class R18Storage extends Recipe
{
   private SqliteDb db;
   private TextField input;
   private Component list;

   public String title()
   {
      return "Storing something";
   }

   public String note()
   {
      return "Add a few notes and reopen the recipe — they are still there. The same code runs on"
         + " desktop, Android, iOS and the web.";
   }

   public void build(Component body) throws Exception
   {
      open();

      Component row = new Component();
      row.setLayout(new GridLayout(Position.START, Position.CENTER));
      input = new TextField();
      input.caption("A note");
      input.testTag("store.input");
      Button add = new Button(MaterialIcon.of(0xE145, 1.4f));   // add
      add.getStyle()
         .backColor(Tones.accent())
         .iconColor(Styles.colorScheme().onPrimary)
         .borderColor(0);
      add.testTag("store.add");
      add.onAction(c -> save());
      row.add(1f, 0f, input, outgap(add, .5f, 0, 0, 0));
      body.add(LAY_AUTO, outgap(row, 0, 0, 0, .8f));

      list = new Component();
      list.setLayout(new VerticalLayout(Position.START, Position.START));
      list.testTag("store.list");
      body.add(LAY_AUTO, list);
      reload();
   }

   private void open()
   {
      String base = Device.homeFolder;
      db = SqliteDb.open(base == null || base.isEmpty() ? "cf-notes.db" : base + "/cf-notes.db");
      db.exec("CREATE TABLE IF NOT EXISTS note(id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT)");
   }

   private void save()
   {
      String t = input.getText()
         .trim();
      if (t.isEmpty())
         return;
      db.execute("INSERT INTO note(text) VALUES(?)", t);   // bound, never concatenated
      input.setText("");
      reload();
      Screen.repaint();
   }

   private void reload()
   {
      try
      {
         list.removeAll();
         // removeAll() takes the layout with it — put it back before adding anything.
         list.setLayout(new VerticalLayout(Position.START, Position.START));
         // Columns named, so position 0 stays position 0 when the table grows a column.
         try (SqliteCursor c = db.query("SELECT id, text FROM note ORDER BY id DESC LIMIT 12"))
         {
            boolean any = false;
            while (c.next())
            {
               any = true;
               list.add(LAY_AUTO, note(c.getLong(0), c.getString(1)));
            }
            if (!any)
               list.add(LAY_AUTO, label("Nothing stored yet.", 1f, false, Styles.captionColor,
                  Position.START));
         }
         // invalidateMeasure, not setValidLayout(false): the list grew, and the scrolling body
         // around it has to measure it again - or the new note came out cut (iPhone, 17/10/2026).
         list.invalidateMeasure();
      }
      catch (Exception e)
      {
         dev.crossfly.util.Debug.handle(e);
      }
   }

   private Component note(final long id, String text) throws Exception
   {
      Card c = new Card();
      c.setLayout(new GridLayout(Position.START, Position.CENTER));
      c.getStyle()
         .outgap(Gap.ofFont(0, 0, 0, .4f));
      Button del = new Button(MaterialIcon.of(0xE872, 1.2f));   // delete
      del.getStyle()
         .iconColor(Tones.danger())
         .borderColor(0)
         .backColor(0);
      del.onAction(e ->
      {
         db.execute("DELETE FROM note WHERE id=?", id);
         reload();
         Screen.repaint();
      });
      c.add(1f, 0f, label(text, 1f, false, Styles.textColor, Position.START), del);
      return c;
   }

   /** Closing when the screen goes away — a connection is a resource, not a value. */
   @Override
   public void onRemove()
   {
      super.onRemove();
      if (db != null)
      {
         db.close();
         db = null;
      }
   }
}
Para una IA

Versiones de esta página legibles por máquina, para que un modelo lea el estante en vez de adivinar la API.

Este ejemplo, en JSONEste ejemplo, en Markdown
AnteriorA list too big to buildSiguienteCapturing a signature