<< All versions

Skill v1.0.0

currentLLM-judged scan90/100
navideck/universal_ble/universal-ble-setup
──Details
PublishedOctober 24, 2026 at 07:27 AM
Content Hashsha256:6ad8a254ee522a6f...
Git SHA026ab079b636
──Files
Files (1 file, 10.2 KB)
SKILL.md10.2 KBactive
SKILL.md · 196 lines · 10.2 KB

version: "1.0.0" name: universal-ble-setup description: >- Use when adding universal_ble to a Flutter app for the first time — BLE scanning, connecting, service discovery, read/write, notifications and indications, or peripheral advertising — or when configuring platform permissions on Android, iOS, macOS, Windows, Linux, or Web, including the Android manifest permissions, the Apple Info.plist keys and macOS Bluetooth entitlement, and Web optional services. Also use when a scan returns nothing, connect times out, a read/write fails, notifications never fire, or the availability state is unsupported or unauthorized.


Adding universal_ble to a Flutter app

universal_ble is a single Flutter plugin for BLE central (and peripheral) on Android, iOS, macOS, Windows, Linux, and Web. This skill gets a first integration working and points to the README for detail. The README anchors below link to `README.md`.

Rules

  1. Depend on universal_ble only. One package covers every platform — Windows

support is built in (no flutter_blue_plus_winrt-style add-on).

  1. Read the README Platform-specific setup

before the first build; missing manifest/plist/entitlement entries make BLE silently fail or the app get killed.

  1. Check availability before scanning: wait for AvailabilityState.poweredOn

via getBluetoothAvailabilityState() or availabilityStream (Bluetooth Availability).

  1. Register onScanResult or listen to scanStream before calling

startScan(); results are per-device BleDevice events, not batches.

  1. startScan() has no timeout: parameter. Stop it yourself with a Timer or

stopScan(), and stop scanning before connecting.

  1. Call discoverServices() after every (re)connection. getService() and

getCharacteristic() auto-discover when the cache is empty, but an explicit call is recommended.

  1. Subscribe before listening: await characteristic.notifications.subscribe()

(or indications.subscribe()), then read characteristic.onValueReceived.

  1. write() defaults to with response. Pass withResponse: false for a

write-without-response.

  1. Permissions are requested automatically on startScan(). Call

requestPermissions() explicitly before connect()/read()/write() if you need them confirmed first.

  1. connect(autoConnect: true) reconnects while the device is available; call

disconnect() to stop it.

  1. UUIDs are format-agnostic: pass "180f", "180F", or the full 128-bit form.

Results always come back lowercase 128-bit.

  1. Peripheral APIs require getCapabilities().supportsPeripheralMode. Linux

and Web do not support peripheral mode.

Setup (central role)

yaml
# pubspec.yaml
dependencies:
universal_ble: ^3.0.0
dart
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:universal_ble/universal_ble.dart';
Future<void> startBle() async {
// 1. Wait until Bluetooth is on. See #bluetooth-availability.
if (await UniversalBle.getBluetoothAvailabilityState() !=
AvailabilityState.poweredOn) {
return;
}
// 2. Listen for results, then scan. See #scanning and #scan-filter.
UniversalBle.scanStream.listen((BleDevice device) {
debugPrint('${device.deviceId} ${device.name} rssi=${device.rssi}');
});
await UniversalBle.startScan(
scanFilter: ScanFilter(withServices: ['180f']),
);
Timer(const Duration(seconds: 15), () => UniversalBle.stopScan());
// 3. Connect and discover. See #connecting and #discovering-services.
final device = BleDevice(deviceId: 'DEVICE_ID', name: null);
await device.connect();
final services = await device.discoverServices();
// 4. Read or subscribe. See #reading--writing-data and #subscriptions.
final characteristic = services
.firstWhere((s) => BleUuidParser.compareStrings(s.uuid, '180f'))
.getCharacteristic('2a19');
final value = await characteristic.read();
await characteristic.notifications.subscribe();
characteristic.onValueReceived.listen((v) => debugPrint('$value $v'));
}

Other topics the README covers in depth: scan filters, system devices, auto-connect and background events, pairing, MTU, RSSI, command queue, timeout, error handling, and logging.

Prefer the object API (bleDevice, characteristic) above. The Low-Level API is a deviceId-based alternative for stateless or service-style code.

Peripheral role (pointer)

Check capabilities before using UniversalBlePeripheral:

dart
final caps = await UniversalBlePeripheral.getCapabilities();
if (!caps.supportsPeripheralMode) return;

Full service management, advertising, request handlers, and event streams are in Peripheral Mode.

Traps

Scan returns nothing

  • Check AvailabilityState.poweredOn and that permissions were granted.
  • Stop any prior scan before starting a new one.
  • Android: for legacy BLE 4.x peripherals (e.g. ESP32) pass

platformConfig: PlatformConfig(android: AndroidOptions(legacy: true)) (Android scan options).

  • Web: you must list the services you will use in the scan filter

(Web).

`AvailabilityState.unsupported` on macOS

  • Symptom: availability reports unsupported even with Bluetooth on.
  • Fix: add the com.apple.security.device.bluetooth entitlement to both

DebugProfile.entitlements and Release.entitlements (macOS entitlements).

`MissingPluginException` right after adding the package

  • Hot reload/restart is not enough. Fully stop and re-run the app; try

flutter clean if it persists.

`connect()` throws or times out

  • Ensure Bluetooth is on, permissions are granted, and scanning is stopped.
  • Catch ConnectionException and inspect e.code

(UniversalBleErrorCode.connectionTimeout, connectionFailed, deviceDisconnected, …) (Error Handling).

`serviceNotFound` / `characteristicNotFound`

  • Call discoverServices() after connecting; UUIDs are format-agnostic.
  • Android keeps a GATT cache across connections; if the peripheral's layout

changed, pass platformConfig: DiscoverServicesPlatformConfig(android: AndroidDiscoverServicesOptions(clearGattCache: true)).

Notifications never fire

  • Subscribe first (notifications.subscribe() or indications.subscribe()),

then listen to onValueReceived.

  • Verify the characteristic advertises notify/indicate in

characteristic.properties.

Stale state after a hot restart

  • Native connections and scans can outlive Dart state in debug hot restart. Use

the resetBleState() helper (Resetting State on Hot Restart).

`write` appears to hang or behaves unexpectedly

  • write() is with-response by default. Use withResponse: false for

write-without-response.

Peripheral APIs throw / do nothing

  • Guard with caps.supportsPeripheralMode; Linux and Web return unsupported.

Where to go next

TaskREADME
Scan filters (services, manufacturer data, name prefix, exclusions)Scan Filter
Devices already connected by the OS / other appsSystem Devices
Auto-connect and background connection eventsAuto-connect
MTU, connection priority, RSSIRequesting MTU · Reading RSSI
Pairing / bondingPairing
DescriptorsReading & Writing data
Subscription statusCheck Subscription Status
Queueing and global timeoutCommand Queue · Timeout
Typed errorsError Handling
Debug loggingLogging
Mock / custom platform implementationCustomizing Platform Implementation
Peripheral serverPeripheral Mode
Coming from flutter_blue_plususe the universal-ble-migrate-from-flutter-blue-plus skill

When in doubt, follow the README — it is the source of truth for this package.

All versions