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.

How to access the database outside the paint thread

Purpose. Heavy queries (scans, bulk, migrations) freeze the UI if they run on CrossFly’s single paint thread. The rule is: do the database I/O on a worker thread and update the UI only via Component.onUI(...), which reroutes the code to the UI thread.

APIs used

  • new Thread(Runnable).start() — creates a worker thread for database access. All SqliteDb/query/bulkInsert runs here, away from the paint thread.
  • Component.onUI(Runnable r) — queues r to run on the UI thread (internally Gdx.app.postRunnable). All component manipulation (setting text, adding items to the list, repaint) must happen inside this callback.
  • SqliteDb / SqliteCursor — opened and closed within the thread itself (via try-with-resources). Do not share a connection between threads; open your own where you’re going to use it.

Golden threading rule. Read/write the database: worker thread. Touch UI components: Component.onUI. Never the other way around — accessing components off the UI thread causes corruption; blocking the paint thread with I/O freezes the app.

Collect before leaving the thread. The SqliteCursor is AutoCloseable and tied to its connection. Do not pass the cursor into onUI. Materialize the data (e.g., into a List) on the worker thread and pass only the finished list to the UI.

Full example (copy, paste and run)

package com.exemplo;

import java.util.*;                          // List, ArrayList

import dev.crossfly.ui.*;                     // Window, Component
import dev.crossfly.ui.layout.*;             // VerticalLayout
import dev.crossfly.sqlite.*;                // SqliteDb, SqliteCursor

/** Runnable main screen. Point the launcher (classname) and run: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      new Thread(() ->
      {
         // --- worker thread: database I/O ---
         List<String> nomes = new ArrayList<>();
         try (SqliteDb db = SqliteDb.open("app.db");
              SqliteCursor c = db.query("SELECT name FROM person ORDER BY name"))
         {
            while (c.next())
               nomes.add(c.getString(0));   // collects BEFORE leaving the thread
         }

         // --- back to the UI only to update components ---
         Component.onUI(() ->
         {
            // here it's safe to touch components (set list, repaint, etc.)
            preencheLista(nomes);
         });
      }).start();
   }

   /** Stub: update your UI components here with the loaded data. */
   private void preencheLista(List<String> nomes)
   {
      // e.g. build items in a list, set texts, repaint()...
   }
}

The snippet below shows only the core. To run it, place it inside the MainWindow skeleton above.

Minimal snippet

// verified in the source: Component.onUI (Gdx.app.postRunnable), SqliteDb.query/cursor 0-based
import dev.crossfly.sqlite.*;
import dev.crossfly.ui.Component;
import java.util.*;

new Thread(() ->
{
   // --- worker thread: database I/O ---
   List<String> nomes = new ArrayList<>();
   try (SqliteDb db = SqliteDb.open("app.db");
        SqliteCursor c = db.query("SELECT name FROM person ORDER BY name"))
   {
      while (c.next())
         nomes.add(c.getString(0));   // collects BEFORE leaving the thread
   }

   // --- back to the UI only to update components ---
   Component.onUI(() ->
   {
      // here it's safe to touch components (set list, repaint, etc.)
      preencheLista(nomes);
   });
}).start();

Dig deeper

Was this page helpful?