Connect to a device
A connection needs two things: a MAC address and a service UUID.
Install#
flutter pub add flutter_classic_bluetoothimport 'package:flutter_classic_bluetooth/flutter_classic_bluetooth.dart';Opening a connection#
final bluetooth = FlutterClassicBluetooth();
final connection = await bluetooth.connect(
address: 'AA:BB:CC:DD:EE:FF',
uuid: BtcUuid.spp,
);
print(connection.isConnected); // trueBtcUuid.spp is 00001101-0000-1000-8000-00805F9B34FB, the Serial Port Profile, and it is the default. Nearly every serial device uses it: ESP32, HC-05, Arduino modules, most thermal printers. Pass a different UUID only if the vendor documents one.
An invalid address throws BtcAddressException and a malformed UUID throws BtcUuidException, both before any radio work happens.
Always set a timeout#
Without one, a connect to a device that is off or out of range can hang for a long time, and how long is up to the platform.
try {
final connection = await bluetooth.connect(
address: device.address,
timeout: const Duration(seconds: 8),
);
} on BtcTimeoutException {
// Device is off, out of range, or already connected to something else.
} on BtcConnectionException catch (e) {
// Refused, or the service UUID is not offered.
print(e.message);
}A native connect cannot be cancelled once it starts. If the attempt succeeds after your deadline has passed, the plugin closes and releases that connection for you, so a late arrival does not leak an open socket and two event channels for the life of the app.
Secure and insecure#
secure defaults to true, which means an authenticated and encrypted RFCOMM channel and a device that must be paired first. Some older modules only accept an insecure channel.
final connection = await bluetooth.connect(
address: device.address,
secure: false,
);Try secure first. Fall back to insecure only when a device refuses, and be aware the link is then unencrypted.
Watching the link#
connection.stateStream.listen((state) {
switch (state) {
case BtcConnectionState.connected:
// ready
case BtcConnectionState.disconnected:
// the device went away
default:
break;
}
});Closing properly#
There are two ways to end a connection, and the difference matters.
await connection.finish(); // flush pending writes, then close
await connection.close(); // close now, drop anything queuedUse finish in almost every case. close is for teardown when you no longer care whether the last bytes arrived, such as in dispose after an error.
Reconnecting on its own#
A serial link over the air drops. If your app should survive that without the user doing anything, use a reconnecting connection instead of managing retries yourself.
final link = bluetooth.connectWithReconnect(
address: 'AA:BB:CC:DD:EE:FF',
policy: const BtcReconnectPolicy(
initialBackoff: Duration(seconds: 1),
maxBackoff: Duration(seconds: 30),
backoffMultiplier: 2.0,
connectTimeout: Duration(seconds: 8),
),
);
link.input.listen((bytes) => handle(bytes));
link.state.listen((s) => print('link: $s'));
await link.sendLine('PING');
await link.close();The input stream survives a drop, so a listener set up once keeps receiving after the link comes back. Backoff grows from initialBackoff by backoffMultiplier up to maxBackoff, which keeps a device that is simply switched off from being hammered.
Several devices at once#
Where the platform allows it, connections are independent and you can hold more than one.
final caps = await bluetooth.getPlatformCapabilities();
if (caps.supportsMultipleConnections) {
final scanner = await bluetooth.connect(address: scannerMac);
final printer = await bluetooth.connect(address: printerMac);
}