Skill v1.0.0
currentLLM-judged scan90/100version: "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
- Depend on
universal_bleonly. One package covers every platform — Windows
support is built in (no flutter_blue_plus_winrt-style add-on).
- Read the README Platform-specific setup
before the first build; missing manifest/plist/entitlement entries make BLE silently fail or the app get killed.
- Check availability before scanning: wait for
AvailabilityState.poweredOn
via getBluetoothAvailabilityState() or availabilityStream (Bluetooth Availability).
- Register
onScanResultor listen toscanStreambefore calling
startScan(); results are per-device BleDevice events, not batches.
startScan()has notimeout:parameter. Stop it yourself with aTimeror
stopScan(), and stop scanning before connecting.
- Call
discoverServices()after every (re)connection.getService()and
getCharacteristic() auto-discover when the cache is empty, but an explicit call is recommended.
- Subscribe before listening:
await characteristic.notifications.subscribe()
(or indications.subscribe()), then read characteristic.onValueReceived.
write()defaults to with response. PasswithResponse: falsefor a
write-without-response.
- Permissions are requested automatically on
startScan(). Call
requestPermissions() explicitly before connect()/read()/write() if you need them confirmed first.
connect(autoConnect: true)reconnects while the device is available; call
disconnect() to stop it.
- UUIDs are format-agnostic: pass
"180f","180F", or the full 128-bit form.
Results always come back lowercase 128-bit.
- Peripheral APIs require
getCapabilities().supportsPeripheralMode. Linux
and Web do not support peripheral mode.
Setup (central role)
# pubspec.yamldependencies:universal_ble: ^3.0.0
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:
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.poweredOnand 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.bluetoothentitlement 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
ConnectionExceptionand inspecte.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()orindications.subscribe()),
then listen to onValueReceived.
- Verify the characteristic advertises
notify/indicatein
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. UsewithResponse: falsefor
write-without-response.
Peripheral APIs throw / do nothing
- Guard with
caps.supportsPeripheralMode; Linux and Web return unsupported.
Where to go next
| Task | README | |
|---|---|---|
| Scan filters (services, manufacturer data, name prefix, exclusions) | Scan Filter | |
| Devices already connected by the OS / other apps | System Devices | |
| Auto-connect and background connection events | Auto-connect | |
| MTU, connection priority, RSSI | Requesting MTU · Reading RSSI | |
| Pairing / bonding | Pairing | |
| Descriptors | Reading & Writing data | |
| Subscription status | Check Subscription Status | |
| Queueing and global timeout | Command Queue · Timeout | |
| Typed errors | Error Handling | |
| Debug logging | Logging | |
| Mock / custom platform implementation | Customizing Platform Implementation | |
| Peripheral server | Peripheral Mode | |
| Coming from flutter_blue_plus | use the universal-ble-migrate-from-flutter-blue-plus skill |
When in doubt, follow the README — it is the source of truth for this package.