This page was machine-translated from Portuguese and may contain errors. The Portuguese version is canonical.
Bluetooth BLE / GATT
CrossFly uses an asynchronous BLE/GATT stack (dev.crossfly.device.bluetooth), which replaced
the old JSR-82/SPP. The modeling is based on CompletableFuture.
⚠️ Honesty note (read before coding)
In the core, several operations are a skeleton over in-memory state — BluetoothConnectionBuilder.build()
creates empty streams, GattCharacteristic.read()/write() only touch the local cache, and discoverServices()
returns an empty list. The real radio transport comes from the platform launchers (Android/iOS). The
modeling (UUIDs, futures, service→characteristic→descriptor hierarchy) is already final; rely on
the API, but test on a real device per platform.
Discovery and connection
BluetoothManager bt = BluetoothManager.getInstance(); // throws IOException
// scan (asynchronous)
bt.scanForDevices(10, TimeUnit.SECONDS, new BluetoothManager.ScanCallback() {
public void onSuccess(List<BluetoothDevice> devices) { /* choose one */ }
public void onError(String error) { Debug.log(error); }
});
// connect (asynchronous) — returns a BluetoothConnection
bt.connect(device, new BluetoothManager.ConnectionCallback() {
public void onSuccess(BluetoothConnection conn) { usar(conn); }
public void onError(String error) { Debug.log(error); }
});
There are *Sync variants (scanForDevicesSync, connectSync) — use them only on a worker thread, never
on the paint thread.
GATT — read the battery and subscribe to notifications
service → characteristic → descriptor model, I/O via CompletableFuture:
device.discoverServices(services -> {
GattService bateria = device.getService("0000180F-0000-1000-8000-00805F9B34FB");
GattCharacteristic nivel = bateria.getCharacteristic("00002A19-0000-1000-8000-00805F9B34FB");
nivel.read().thenAccept(bytes -> {
int pct = bytes[0] & 0xFF;
Component.onUI(() -> label.setText(pct + "%"));
});
nivel.enableNotifications(bytes -> // subscribes to changes
Component.onUI(() -> label.setText((bytes[0] & 0xFF) + "%")));
});
GattCharacteristic exposes read() → CompletableFuture<byte[]>, write(byte[]) → CompletableFuture<Void>,
enableNotifications(Consumer<byte[]>), and the capability checks canRead()/canWrite()/canNotify().
BluetoothConnection is AutoCloseable
try (BluetoothConnection conn = bt.connectSync(device)) { // only on a worker thread
conn.write("PING".getBytes());
byte[] resp = conn.read();
} // closes on its own
Best practices
- Async on the UI (
connect(...)),*Synconly on a worker. - Callbacks/futures return to the UI with
Component.onUI(...). try-with-resourceson theBluetoothConnection.- Test on a real device per platform (the core is modeling; the radio lives in the launcher).
Common mistakes
- Calling
*Syncvariants on the paint thread (blocks the UI). - Expecting the core to do real radio I/O (it’s a skeleton; the transport belongs to the platform).
- Touching the UI inside
thenAccept/the callback withoutonUI(...).