This page was machine-translated from Portuguese and may contain errors. The Portuguese version is canonical.
How to scan and connect to a Bluetooth device (BLE)
Purpose. Discover nearby Bluetooth devices, connect to one of them, and exchange data — by stream (Classic/SPP) or by GATT characteristics (BLE: read, write, receive notifications).
⚠️ Honesty note (read before using)
In the
coremodule, the BLE layer is a skeleton over an in-memory cache, not the real radio:
GattCharacteristic.read()returns the cached value (CompletableFuture.completedFuture(value)), not a hardware read.GattCharacteristic.write(byte[])only stores the value locally.enableNotifications(...)registers the listener, but the data is triggered bynotifyValueChanged(...).BluetoothDevice.discoverServices(...)returns an empty list in core.The real radio (actual scan/connection/GATT) is provided by the platform launchers (Android/iOS). Treat the code below as the public API; the effective behavior depends on the launcher the application runs on.
APIs used
BluetoothManager.getInstance() throws IOException— singleton.scanForDevices(long duration, TimeUnit unit, ScanCallback cb)— asynchronous discovery;ScanCallback.onSuccess(List<BluetoothDevice>)/onError(String).connect(BluetoothDevice device, ConnectionCallback cb)— asynchronous connection;ConnectionCallback.onSuccess(BluetoothConnection)/onError(String).BluetoothConnection(AutoCloseable) —read()/read(int max)→byte[];write(byte[])/write(String)→int.- GATT:
GattCharacteristic.read()→CompletableFuture<byte[]>;write(byte[])→CompletableFuture<Void>;enableNotifications(Consumer<byte[]>)→CompletableFuture<Void>. BluetoothManager.isBluetoothEnabled(),getPairedDevices(...),getLocalDevice().
Complete example (copy, paste, and run)
Remember the honesty note above: in the
coremodule the BLE layer is a skeleton over an in-memory cache — the real radio comes from the platform launchers (Android/iOS). The example below is the public API; the effective behavior depends on the launcher the application runs on.
package com.exemplo;
import java.util.concurrent.*; // TimeUnit
import dev.crossfly.ui.*; // Window, Component, Button, Label
import dev.crossfly.ui.layout.*; // VerticalLayout
import dev.crossfly.device.bluetooth.*; // BluetoothManager, BluetoothDevice
/** 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());
Label resultado = new Label("");
add(LAY_AUTO, resultado);
Button acao = new Button("Escanear Bluetooth");
acao.onAction(c ->
{
try
{
BluetoothManager bt = BluetoothManager.getInstance();
// Scan (callbacks outside the paint thread → onUI when touching the UI)
bt.scanForDevices(10, TimeUnit.SECONDS, new BluetoothManager.ScanCallback()
{
public void onSuccess(java.util.List<BluetoothDevice> devices)
{
Component.onUI(() -> resultado.setText("Encontrados: " + devices.size()));
}
public void onError(String error)
{
Component.onUI(() -> resultado.setText("Erro: " + error));
}
});
}
catch (java.io.IOException e)
{
resultado.setText("Bluetooth indisponível"); // Bluetooth system unavailable
}
});
add(LAY_AUTO, acao);
}
}
The snippet below shows only the core. To run it, place it in the
MainWindowskeleton above.
Minimal snippet
// verified at source: BluetoothManager.java (getInstance, scanForDevices, connect); BluetoothConnection.java (read/write); GattCharacteristic.java (read/write/enableNotifications)
import java.util.concurrent.*; // TimeUnit
import dev.crossfly.device.bluetooth.*; // BluetoothManager, BluetoothDevice, BluetoothConnection, GattCharacteristic
import dev.crossfly.ui.*; // Component (onUI)
try {
BluetoothManager bt = BluetoothManager.getInstance();
// 1) Scan (callbacks outside the paint thread → onUI when touching the UI)
bt.scanForDevices(10, TimeUnit.SECONDS, new BluetoothManager.ScanCallback() {
public void onSuccess(java.util.List<BluetoothDevice> devices) {
Component.onUI(() -> listarDispositivos(devices));
}
public void onError(String error) {
Component.onUI(() -> mostrarErro(error));
}
});
// 2) Connect and exchange data over a stream (Classic/SPP)
BluetoothDevice alvo = /* chosen by the user */;
bt.connect(alvo, new BluetoothManager.ConnectionCallback() {
public void onSuccess(BluetoothConnection conn) {
try (conn) { // AutoCloseable
conn.write("PING".getBytes());
byte[] resposta = conn.read(); // up to 4096 bytes
} catch (java.io.IOException e) { /* handle it */ }
}
public void onError(String error) {
Component.onUI(() -> mostrarErro(error));
}
});
// 3) BLE / GATT — read and subscribe to notifications of a characteristic
GattCharacteristic ch = /* obtained from the device's service */;
ch.enableNotifications(data ->
Component.onUI(() -> atualizarValor(data))); // Consumer<byte[]>
ch.read().thenAccept(valor ->
Component.onUI(() -> mostrarValor(valor))); // CompletableFuture<byte[]>
} catch (java.io.IOException e) {
// Bluetooth system unavailable
}
Go deeper
- Bluetooth/BLE guide: Bluetooth & BLE