Scan for devices
Two ways to find a device: collect a scan, or watch results arrive one at a time.
Install#
flutter pub add flutter_classic_bluetoothimport 'package:flutter_classic_bluetooth/flutter_classic_bluetooth.dart';The simple way#
scan runs discovery for a fixed window and hands back a finished list, deduplicated by address and sorted strongest signal first.
final bluetooth = FlutterClassicBluetooth();
final devices = await bluetooth.scan(timeout: const Duration(seconds: 10));
for (final device in devices) {
print('${device.displayName} ${device.address} ${device.rssi} dBm');
}Discovery is stopped for you when the window closes, including if something throws partway through.
A live list#
Ten seconds of a blank screen is a poor experience. Listen to discoveryResults and show each device the moment it appears.
final found = <String, BtcDevice>{};
final sub = bluetooth.discoveryResults.listen((device) {
setState(() => found[device.address] = device);
});
await bluetooth.startDiscovery();
// ... later
await bluetooth.stopDiscovery();
await sub.cancel();Key the map by address. The same device is reported several times during a scan, once per inquiry response, and each report may carry a different RSSI or fill in a name the first one lacked.
Knowing when it stops#
Discovery ends on its own after the platform's inquiry window, whether or not you call stopDiscovery. discoveryState tells you when that happens so a spinner can stop spinning.
bluetooth.discoveryState.listen((scanning) {
setState(() => isScanning = scanning);
});Devices you already know#
Most of the time the device is already paired, and a scan is wasted effort. Paired devices are available instantly, with no permission prompt on most platforms and no radio activity.
final paired = await bluetooth.getPairedDevices();
final printer = paired.firstWhere(
(d) => d.name?.contains('Printer') ?? false,
);A good device picker shows paired devices first and offers a scan for anything new.
What a result carries#
| Field | Meaning |
|---|---|
address | MAC address, the stable identifier |
name | Advertised name, often null on the first report |
alias | User-assigned name, where the platform has one |
displayName | alias, else name, else the address |
rssi | Signal strength in dBm, closer to zero is stronger |
type | Classic, LE, or dual mode |
bondState | Whether it is already paired |
uuids | Advertised service UUIDs, when the platform reports them |
Use displayName in your UI. A device that has not reported a name yet would otherwise render as an empty row.
Filtering to serial devices#
A scan picks up headphones, keyboards and watches along with the board you care about. Where uuids is populated you can narrow the list to devices offering a serial port.
final serial = devices.where(
(d) => d.uuids.any((u) => u.toUpperCase() == BtcUuid.spp),
);Do not rely on this alone. Several platforms report an empty uuids list during discovery and only fill it in after pairing, so treat a match as a hint and keep the full list reachable.
Not available on iOS#
iOS has no discovery API for Bluetooth Classic. startDiscovery and scan throw BtcUnsupportedException there, and getPairedDevices returns the MFi accessories your app declared. Check first:
final caps = await bluetooth.getPlatformCapabilities();
if (!caps.canDiscoverDevices) {
// Offer the paired list instead of a scan button.
}