flutter_classic_bluetooth v1.0.0
pub.dev GitHub

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_bluetooth
import '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#

FieldMeaning
addressMAC address, the stable identifier
nameAdvertised name, often null on the first report
aliasUser-assigned name, where the platform has one
displayNamealias, else name, else the address
rssiSignal strength in dBm, closer to zero is stronger
typeClassic, LE, or dual mode
bondStateWhether it is already paired
uuidsAdvertised 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.
}