A cross-platform (Android/iOS/macOS/Windows/Linux) plugin for managing Bluetooth accessories and HID devices in Flutter.
| Android | iOS | macOS | Windows | Linux | |
|---|---|---|---|---|---|
| showBluetoothAccessoryPicker | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
| startScan/stopScan | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
| pair/unpair | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
| getPairedDevices | ✔️ | ❌ | ✔️ | ✔️ | ✔️ |
| connect (HID) | ✔️ | ❌ | ✔️ | ✔️ | ❌ |
| disconnect | ✔️ | ❌ | ✔️ | ✔️ | ✔️* |
| sendReport | ✔️ | ❌ | ✔️ | ✔️ | ❌ |
| setupSdp/closeSdp | ✔️ | ❌ | ✔️ | ✔️ | ❌ |
| closeEASession | ❌ | ✔️ | ❌ | ❌ | ❌ |
| accessoryConnected/Disconnected | ❌ | ✔️ | ❌ | ❌ | ❌ |
| onConnectionStateChanged (HID) | ✔️ | ❌ | ✔️ | ✔️ | ❌ |
| onGetReport | ✔️ | ❌ | ✔️ | ✔️ | ❌ |
| onSdpServiceRegistrationUpdate | ✔️ | ❌ | ✔️ | ✔️ | ❌ |
*Linux disconnect() is basic disconnect only, not HID-specific.
Add flutter_accessory_manager in your pubspec.yaml:
dependencies:
flutter_accessory_manager:and import it wherever you want to use it:
import 'package:flutter_accessory_manager/flutter_accessory_manager.dart';Start scanning for Bluetooth devices:
await FlutterAccessoryManager.startScan();Stop scanning for Bluetooth devices:
await FlutterAccessoryManager.stopScan();Check if currently scanning:
bool isScanning = await FlutterAccessoryManager.isScanning();Listen to discovered devices:
FlutterAccessoryManager.onBluetoothDeviceDiscover = (BluetoothDevice device) {
print('Device discovered: ${device.name} (${device.address})');
print('RSSI: ${device.rssi}');
print('Paired: ${device.paired}');
print('Device Type: ${device.deviceType}');
print('Device Class: ${device.deviceClass}');
};Listen to device removal events:
FlutterAccessoryManager.onBluetoothDeviceRemoved = (BluetoothDevice device) {
print('Device removed: ${device.name} (${device.address})');
};Get a list of all paired devices:
List<BluetoothDevice> devices = await FlutterAccessoryManager.getPairedDevices();
for (var device in devices) {
print('Paired device: ${device.name} - ${device.address}');
}Show the native Bluetooth accessory picker dialog. On iOS, this displays the External Accessory picker.
await FlutterAccessoryManager.showBluetoothAccessoryPicker();Optionally filter by device names:
await FlutterAccessoryManager.showBluetoothAccessoryPicker(
withNames: ['MyDevice', 'AnotherDevice'],
);Note: Not available on Linux.
Pair with a Bluetooth device by its address:
bool success = await FlutterAccessoryManager.pair('00:11:22:33:44:55');
if (success) {
print('Device paired successfully');
} else {
print('Pairing failed');
}Unpair a Bluetooth device:
await FlutterAccessoryManager.unpair('00:11:22:33:44:55');Connect to a Bluetooth HID device:
await FlutterAccessoryManager.connect('00:11:22:33:44:55');Platform Note: Available on Android, macOS, and Windows. Not available on iOS (uses External Accessory framework) or Linux.
Disconnect from a Bluetooth device:
await FlutterAccessoryManager.disconnect('00:11:22:33:44:55');Listen to connection state changes:
FlutterAccessoryManager.onConnectionStateChanged = (String deviceId, bool connected) {
print('Device $deviceId: ${connected ? "connected" : "disconnected"}');
};Platform Note: Available on Android, macOS, and Windows (HID connections only). Not available on iOS or Linux.
Send a HID report to a connected device:
import 'dart:typed_data';
Uint8List reportData = Uint8List.fromList([0x01, 0x02, 0x03]);
await FlutterAccessoryManager.sendReport('00:11:22:33:44:55', reportData);Platform Note: Available on Android, macOS, and Windows. Not available on iOS or Linux.
Handle HID get report requests:
import 'dart:typed_data';
FlutterAccessoryManager.onGetReport = (String deviceId, ReportType type, int bufferSize) {
print('Get report request from $deviceId, type: $type, size: $bufferSize');
// Return a report reply
return ReportReply(
data: Uint8List.fromList([0x01, 0x02, 0x03]),
error: null,
);
};Platform Note: Available on Android, macOS, and Windows. Not available on iOS or Linux.
Set up the SDP service registration for Bluetooth HID:
import 'dart:typed_data';
SdpConfig config = SdpConfig(
macSdpConfig: MacSdpConfig(
data: {
'ServiceName': 'My HID Service',
// ... other SDP data
},
),
androidSdpConfig: AndroidSdpConfig(
name: 'My HID Service',
description: 'HID Service Description',
provider: 'My Company',
subclass: 0x2540,
descriptors: Uint8List.fromList([/* HID descriptors */]),
),
);
await FlutterAccessoryManager.setupSdp(config: config);Close the SDP service registration:
await FlutterAccessoryManager.closeSdp();Listen to SDP service registration status changes:
FlutterAccessoryManager.onSdpServiceRegistrationUpdate = (bool registered) {
print('SDP service ${registered ? "registered" : "unregistered"}');
};Platform Note: Available on Android, macOS, and Windows. Not available on iOS or Linux.
⚠️ iOS Only: The following APIs are only available on iOS. On other platforms, they will throwUnimplementedError.
Close an External Accessory session. If no protocol string is provided, it will use the first available protocol:
await FlutterAccessoryManager.closeEASession('com.mycompany.myprotocol');Listen to iOS External Accessory connection events:
FlutterAccessoryManager.accessoryConnected = (EAAccessory accessory) {
print('Accessory connected: ${accessory.name}');
print('Manufacturer: ${accessory.manufacturer}');
print('Model: ${accessory.modelNumber}');
print('Protocols: ${accessory.protocolStrings}');
};Listen to iOS External Accessory disconnection events:
FlutterAccessoryManager.accessoryDisconnected = (EAAccessory accessory) {
print('Accessory disconnected: ${accessory.name}');
};Represents a Bluetooth device:
BluetoothDevice device = ...;
print('Address: ${device.address}');
print('Name: ${device.name}');
print('Paired: ${device.paired}');
print('Connected with HID: ${device.isConnectedWithHid}');
print('RSSI: ${device.rssi}');
print('Device Type: ${device.deviceType}'); // classic, le, dual, unknown
print('Device Class: ${device.deviceClass}'); // peripheral, audioVideo, etc.Represents an iOS External Accessory:
EAAccessory accessory = ...;
print('Name: ${accessory.name}');
print('Manufacturer: ${accessory.manufacturer}');
print('Model: ${accessory.modelNumber}');
print('Serial: ${accessory.serialNumber}');
print('Firmware: ${accessory.firmwareRevision}');
print('Hardware: ${accessory.hardwareRevision}');
print('Dock Type: ${accessory.dockType}');
print('Protocols: ${accessory.protocolStrings}');
print('Connected: ${accessory.isConnected}');
print('Connection ID: ${accessory.connectionID}');Configuration for SDP service registration:
SdpConfig config = SdpConfig(
macSdpConfig: MacSdpConfig(
sdpPlistFile: 'path/to/plist', // optional
data: {
'ServiceName': 'My Service',
// ... other SDP data
},
),
androidSdpConfig: AndroidSdpConfig(
name: 'My HID Service',
description: 'Service Description',
provider: 'My Company',
subclass: 0x2540,
descriptors: Uint8List.fromList([/* HID descriptors */]),
),
);Reply to a HID get report request:
ReportReply reply = ReportReply(
data: Uint8List.fromList([0x01, 0x02, 0x03]),
error: null, // null if successful, error code otherwise
);enum DeviceType {
classic, // Classic Bluetooth
le, // Bluetooth Low Energy
dual, // Dual mode (Classic + LE)
unknown, // Unknown type
}enum DeviceClass {
audioVideo,
computer,
health,
imaging,
misc,
networking,
peripheral,
phone,
toy,
uncategorized,
wearable,
}enum ReportType {
input, // Input report
output, // Output report
feature, // Feature report
}Add the following permissions to your AndroidManifest.xml:
<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" 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" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" />Set minimum SDK to 23 in your build.gradle:
android {
defaultConfig {
minSdkVersion 23
}
}You need to programmatically request permissions on runtime. You could use a package such as permission_handler.
For Android 12+, request Permission.bluetoothScan and Permission.bluetoothConnect.
For Android 11 and below, request Permission.location.
Add Bluetooth accessory protocols to your Info.plist:
<key>UISupportedExternalAccessoryProtocols</key>
<array>
<string>com.yourcompany.yourapp.protocol</string>
</array>For macOS, add the Bluetooth capability to your app from Xcode.
Your Bluetooth adapter needs to support at least Bluetooth 4.0. If you have more than 1 adapter, the first one returned from the system will be picked.
When publishing on Windows, you need to declare the following capabilities: bluetooth, radios.
When publishing on Linux as a snap, you need to declare the bluez plug in snapcraft.yaml:
...
plugs:
- bluezThe following APIs are only available on iOS:
closeEASession([String? protocolString])- Closes an External Accessory sessionaccessoryConnectedcallback - Triggered when an iOS External Accessory is connectedaccessoryDisconnectedcallback - Triggered when an iOS External Accessory is disconnected
These APIs use the EAAccessory type which is iOS-specific. They will only be triggered on iOS when External Accessories are connected or disconnected.
The following HID-related APIs are not available on iOS (which uses External Accessory framework instead) and not available on Linux:
connect(String deviceId)- Connect to HID devicesendReport(String deviceId, Uint8List data)- Send HID reportsetupSdp({required SdpConfig config})- Setup SDP servicecloseSdp()- Close SDP serviceonGetReportcallback - Handle HID get report requestsonSdpServiceRegistrationUpdatecallback - SDP registration updatesonConnectionStateChangedcallback - HID connection state changes
Available on: Android, macOS, Windows
Not available on: iOS, Linux
Note: Linux does have disconnect() but not the other HID methods.
On Linux, the following APIs are not implemented:
showBluetoothAccessoryPicker()- Native picker not availableconnect()- HID connection not supportedsendReport()- HID reports not supportedsetupSdp()- SDP service not supportedcloseSdp()- SDP service not supported
Available on Linux:
startScan(),stopScan(),isScanning()pair(),unpair()getPairedDevices()disconnect()(basic disconnect only)onBluetoothDeviceDiscover,onBluetoothDeviceRemovedcallbacks
// Create a class that extends FlutterAccessoryManagerInterface
class FlutterAccessoryManagerMock extends FlutterAccessoryManagerInterface {
// Implement all methods
}
// Set custom platform specific implementation (e.g. for testing)
FlutterAccessoryManager.setInstance(FlutterAccessoryManagerMock());Here are some of the apps leveraging the power of flutter_accessory_manager in production:
| BT Cam A Bluetooth remote app for DSLR and mirrorless cameras. Compatible with Canon, Nikon, Sony, Fujifilm, GoPro, Olympus, Panasonic, Pentax, and Blackmagic. Built using Flutter Accessory Manager to connect and control cameras across iOS, Android, macOS, Windows, Linux & Web. |
|---|
💡 Built something cool with Flutter Accessory Manager?
We'd love to showcase your app here!
Open a pull request and add it to this section. Please include your app icon in svg!