flutter_classic_bluetooth 0.1.1 copy "flutter_classic_bluetooth: ^0.1.1" to clipboard
flutter_classic_bluetooth: ^0.1.1 copied to clipboard

Bluetooth Classic (RFCOMM/SPP) serial plugin for Flutter — discover, pair, connect and exchange data on Android, Windows, macOS, Linux and iOS (MFi).

pub version pub points pub likes GitHub stars GitHub forks GitHub issues CI status Last commit License: MIT Dart Flutter

Bluetooth Classic (RFCOMM) Plugin for Flutter #

flutter_classic_bluetooth is a Flutter plugin for Bluetooth Classic serial communication over RFCOMM (the Serial Port Profile, SPP). It lets you discover, pair, connect to, and exchange data with Bluetooth Classic devices — ESP32, ESP8266, Arduino boards, HC-05 / HC-06 modules, barcode scanners, thermal printers, OBD-II adapters, and other serial / UART peripherals — from a single Dart API on Android, Windows, macOS, Linux, and iOS (MFi). Connections are exposed as Dart streams, so reading and writing bytes feels like any other Stream/Sink.

Find this useful? Star it on GitHub and 👍 like it on pub.dev — it helps other Flutter developers find a maintained Bluetooth Classic plugin.

Overview #

flutter_classic_bluetooth speaks RFCOMM/SPP, the classic Bluetooth serial transport — not Bluetooth Low Energy (BLE). It wraps each platform's native stack (Android BluetoothSocket, Windows Winsock2 AF_BTH, Linux BlueZ RFCOMM sockets, macOS IOBluetooth, iOS ExternalAccessory) behind one consistent Dart interface. You can act as a client (connect out to a device) or as a server (advertise an SDP service and accept incoming connections), run several connections at once, and observe adapter, discovery, and bond state through broadcast streams.

What you can do with it:

  • Discover nearby devices and list paired/bonded devices.
  • Connect over RFCOMM by MAC address + service UUID, then read and write a byte stream.
  • Run an RFCOMM server that accepts incoming client connections.
  • Pair/unpair devices, toggle the adapter, and make the device discoverable (where the platform allows).
  • Query per-platform capabilities at runtime so your UI only offers what works.

Table of contents #

Key features #

A complete Bluetooth Classic (RFCOMM/SPP) client + server toolkit behind one Dart API. Expand a group for details:

📡 Connectivity
  • RFCOMM/SPP client — connect by address + service UUID, secure or insecure
  • RFCOMM server — advertise an SDP service and accept incoming clients
  • Multiple simultaneous connections, each with its own id
  • Optional connection timeout
🔍 Discovery & pairing
  • Device discovery with results and start/stop state streams
  • Paired/bonded device listing
  • Bond / unbond devices and observe bond-state changes
  • Make discoverable (where supported)
🔀 Streamed I/O
  • Incoming bytes as a Dart Stream<Uint8List>
  • Ordered write sink — add, writeBytes, writeString, addStream, allSent
  • Connection-state stream (connectingconnecteddisconnectingdisconnected)
🧩 Adapter & capabilities
  • Adapter state stream, name, and address
  • Enable/disable the adapter (Android)
  • Runtime platform-capability matrix so the UI adapts per platform
🛡️ Reliability
  • Typed exception hierarchy — BtcException + subtypes
  • Main-thread-safe event delivery on every platform
  • Honest per-platform capability reporting (no dead code paths)

Platform support #

Bluetooth Classic capabilities differ by OS, so the plugin reports what each one can actually do (also queryable at runtime via getPlatformCapabilities()):

Feature Android Windows macOS Linux iOS
Adapter state stream
Discover devices
Get paired devices ✅¹
Pair (bond) ✅² ✅³
Unpair (unbond) ❌⁴ ✅³
Connect (RFCOMM) ✅¹
Server mode
Enable / Disable ✅³
Set discoverable

¹ iOS uses the ExternalAccessory framework — only MFi-certified accessories are supported, and the uuid argument is treated as the MFi protocol string.
² macOS pairs via IOBluetoothDevicePair and may show a system pairing prompt.
³ Linux uses the BlueZ D-Bus API (org.bluez); pairing a device that needs a PIN/passkey requires a system pairing agent.
⁴ macOS has no public API to remove an existing pairing — unpair via System Settings.

Example #

A complete, runnable demo app lives in the example/ directory, with screens for adapter control, device discovery, paired devices, RFCOMM client/server, and the platform-capabilities matrix. Clone the repository and run it, or copy any snippet from Getting started below.

Installation #

flutter pub add flutter_classic_bluetooth

Then import it:

import 'package:flutter_classic_bluetooth/flutter_classic_bluetooth.dart';

Platform setup #

Android — add the Bluetooth permissions to android/app/src/main/AndroidManifest.xml:

<!-- Android 11 (API 30) and below -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<!-- Android 12 (API 31) and above -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

iOS — declare the MFi protocol(s) and a usage string in ios/Runner/Info.plist:

<key>UISupportedExternalAccessoryProtocols</key>
<array>
  <string>com.example.spp</string>
</array>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app communicates with Bluetooth accessories.</string>

macOS — add the Bluetooth entitlement to macos/Runner/*.entitlements and a usage string to Info.plist:

<key>com.apple.security.device.bluetooth</key>
<true/>

Linux — install the GTK and BlueZ development packages the native plugin builds against (Debian/Ubuntu shown; the build fails with a gtk+-3.0 or bluetooth/bluetooth.h CMake error if they're missing):

sudo apt-get install -y libgtk-3-dev libbluetooth-dev ninja-build cmake pkg-config clang

On Fedora use gtk3-devel bluez-libs-devel ninja-build cmake clang; on Arch, gtk3 bluez-libs ninja cmake clang.

Getting started #

Initialize and check support #

final bluetooth = FlutterClassicBluetooth();

final supported = await bluetooth.isSupported();
final enabled = await bluetooth.isEnabled();
final caps = await bluetooth.getPlatformCapabilities();

if (caps.canDiscoverDevices) {
  // safe to call startDiscovery() on this platform
}

Discover nearby devices #

final sub = bluetooth.discoveryResults.listen((device) {
  print('Found: ${device.displayName} (${device.address})');
});

await bluetooth.startDiscovery();
// ...later
await bluetooth.stopDiscovery();
await sub.cancel();

List paired devices #

final devices = await bluetooth.getPairedDevices();
for (final device in devices) {
  print('${device.displayName} — ${device.address} [${device.bondState.name}]');
}

Connect to a device #

// SPP is the default — for HC-05/06, ESP32, Arduino, etc. this is all you need:
final connection = await bluetooth.connect(address: 'AA:BB:CC:DD:EE:FF');
print('Connected: id=${connection.id}');

// Override the UUID and tune the attempt only when you need to:
final custom = await bluetooth.connect(
  address: 'AA:BB:CC:DD:EE:FF',
  uuid: BtcUuid.spp, // or any service UUID string
  secure: true,
  timeout: const Duration(seconds: 15), // optional
);

Receive data #

connection.input.listen(
  (Uint8List data) => print('Received ${data.length} bytes: $data'),
  onDone: () => print('Remote closed the connection'),
);

Send data #

// Raw bytes
await connection.output.add(Uint8List.fromList([0x01, 0x02, 0x03]));

// Convenience helpers
await connection.output.writeBytes([0x04, 0x05]);
await connection.output.writeString('AT+RESET\r\n');

// Wait until everything queued so far has been written
await connection.output.allSent;

Watch the connection state #

connection.stateStream.listen((state) {
  print('State: ${state.name}'); // connected, disconnecting, disconnected, ...
});

print(connection.isConnected); // true while connected

Disconnect and dispose #

await connection.finish(); // flush pending writes, then disconnect
// or: await connection.close(); // disconnect immediately

connection.dispose(); // always release resources when done

Run an RFCOMM server #

final server = await bluetooth.startServer(
  serviceName: 'MyService',
  uuid: BtcUuid.spp, // optional — SPP is the default
  secure: true,
);

server.connections.listen((client) {
  print('Client connected: ${client.address}');
  client.input.listen((data) => client.output.writeString('echo: '));
});

// ...later
await server.close();

Pair and unpair #

if (caps.canBondDevices) {
  final ok = await bluetooth.bondDevice('AA:BB:CC:DD:EE:FF');
  print('Bonded: $ok');
}

bluetooth.bondState('AA:BB:CC:DD:EE:FF').listen((state) {
  print('Bond state: ${state.name}');
});

await bluetooth.unbondDevice('AA:BB:CC:DD:EE:FF');

Adapter state and control #

bluetooth.adapterState.listen((state) {
  print('Adapter: ${state.name}'); // on, off, turningOn, ...
});

if (caps.canEnableBluetooth) {
  await bluetooth.enableBluetooth(); // Android: shows the system dialog
}

if (caps.canSetDiscoverable) {
  await bluetooth.setDiscoverable(120); // seconds
}

Handle errors #

try {
  await bluetooth.connect(address: addr, uuid: uuid);
} on BtcUnsupportedException catch (e) {
  print('${e.feature} not supported on ${e.platform}');
} on BtcDisabledException {
  print('Turn on Bluetooth first');
} on BtcTimeoutException {
  print('Connection timed out');
} on BtcConnectionException catch (e) {
  print('Connection failed: ${e.message}');
} on BtcException catch (e) {
  print('Bluetooth error: ${e.message}');
}

Every failure throws a typed BtcException (or a subtype): BtcUnsupportedException, BtcPermissionException, BtcDisabledException, BtcConnectionException, BtcWriteException, BtcDiscoveryException, BtcTimeoutException, BtcAddressException, and BtcUuidException.

FAQ #

Is this Bluetooth Classic or Bluetooth Low Energy (BLE)? Bluetooth Classic — RFCOMM/SPP serial communication. For BLE, use a BLE-specific package; this plugin targets classic serial peripherals like ESP32, HC-05/HC-06, printers, and scanners.

Does it work with ESP32, ESP8266, Arduino, and HC-05/HC-06 modules? Yes. Any device that exposes a Bluetooth Classic RFCOMM/SPP serial profile works: an ESP32 using BluetoothSerial, an Arduino or ESP8266 wired to an HC-05/HC-06 module, and other UART-over-Bluetooth peripherals (thermal printers, barcode scanners, OBD-II adapters). Pair the device, then just connect(address: ...) — the SPP UUID (BtcUuid.spp) is used by default.

Which platforms are supported? Android, Windows, macOS, and Linux for full client/server RFCOMM; iOS supports only MFi-certified accessories via the ExternalAccessory framework (no discovery or server mode). See Platform support.

Why does iOS behave differently? Apple restricts general Bluetooth Classic access to MFi-certified accessories. On iOS the uuid you pass to connect() is treated as the MFi protocol string, and discovery/pairing/server features are unavailable by platform design.

Can I have several connections open at once? Yes. Each connect() (and each accepted server client) returns an independent BtcConnection with its own input/output streams.

How do I know if a feature works on the current device? Call getPlatformCapabilities() and check the matching flag (e.g. canDiscoverDevices, canCreateServer) before invoking it — the plugin reports capabilities honestly per platform.

How does pairing work on macOS and Linux? On macOS, bondDevice pairs via IOBluetoothDevicePair (which may show a system prompt for PIN/passkey devices); removing a pairing has no public API, so unpair through System Settings. On Linux, bondDevice/unbondDevice use the BlueZ D-Bus API directly; devices that require a PIN or passkey additionally need a system pairing agent (e.g. a running desktop Bluetooth applet).

Support and feedback #

  • Found a bug or want a feature? Open an issue on the issue tracker.
  • Questions and ideas are welcome via GitHub Discussions.
  • Pull requests are welcome — see the repository for contribution guidelines.

About #

flutter_classic_bluetooth is an open-source, MIT-licensed Flutter plugin for Bluetooth Classic (RFCOMM/SPP) serial communication across Android, Windows, macOS, Linux, and iOS (MFi), exposing native Bluetooth stacks through one stream-based Dart API.

flutter_classic_bluetooth is created and owned by Nurullah Al Masum.

Contributors #

flutter_classic_bluetooth grows with its community — every contributor is listed here:

flutter_classic_bluetooth contributors

Want to help? Pull requests are welcome — see Support and feedback.

32
likes
0
points
512
downloads

Publisher

verified publisheralmasum.dev

Weekly Downloads

Bluetooth Classic (RFCOMM/SPP) serial plugin for Flutter — discover, pair, connect and exchange data on Android, Windows, macOS, Linux and iOS (MFi).

Repository (GitHub)
View/report issues

Topics

#bluetooth #bluetooth-classic #rfcomm #serial #iot

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on flutter_classic_bluetooth

Packages that implement flutter_classic_bluetooth