Skip to content

Navideck/flutter_accessory_manager

Repository files navigation

Flutter Accessory Manager

flutter_accessory_manager version

A cross-platform (Android/iOS/macOS/Windows/Linux) plugin for managing Bluetooth accessories and HID devices in Flutter.

Features

API Support

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.

Getting Started

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';

Scanning

Start Scanning

Start scanning for Bluetooth devices:

await FlutterAccessoryManager.startScan();

Stop Scanning

Stop scanning for Bluetooth devices:

await FlutterAccessoryManager.stopScan();

Check Scanning Status

Check if currently scanning:

bool isScanning = await FlutterAccessoryManager.isScanning();

Device Discovery

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}');
};

Device Removed

Listen to device removal events:

FlutterAccessoryManager.onBluetoothDeviceRemoved = (BluetoothDevice device) {
  print('Device removed: ${device.name} (${device.address})');
};

Get Paired Devices

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 Bluetooth Accessory Picker

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.

Pairing & Unpairing

Pair

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

Unpair a Bluetooth device:

await FlutterAccessoryManager.unpair('00:11:22:33:44:55');

Connecting

Connect (HID)

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

Disconnect from a Bluetooth device:

await FlutterAccessoryManager.disconnect('00:11:22:33:44:55');

Connection State Changes

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.

HID Reports

Send Report

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.

Get Report

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.

SDP Service Registration

Setup SDP

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 SDP

Close the SDP service registration:

await FlutterAccessoryManager.closeSdp();

SDP Registration Updates

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 External Accessory

⚠️ iOS Only: The following APIs are only available on iOS. On other platforms, they will throw UnimplementedError.

Close EA Session

Close an External Accessory session. If no protocol string is provided, it will use the first available protocol:

await FlutterAccessoryManager.closeEASession('com.mycompany.myprotocol');

Accessory Connected

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}');
};

Accessory Disconnected

Listen to iOS External Accessory disconnection events:

FlutterAccessoryManager.accessoryDisconnected = (EAAccessory accessory) {
  print('Accessory disconnected: ${accessory.name}');
};

Data Types

BluetoothDevice

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.

EAAccessory (iOS)

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}');

SdpConfig

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 */]),
  ),
);

ReportReply

Reply to a HID get report request:

ReportReply reply = ReportReply(
  data: Uint8List.fromList([0x01, 0x02, 0x03]),
  error: null, // null if successful, error code otherwise
);

Enums

DeviceType

enum DeviceType {
  classic,  // Classic Bluetooth
  le,       // Bluetooth Low Energy
  dual,     // Dual mode (Classic + LE)
  unknown,  // Unknown type
}

DeviceClass

enum DeviceClass {
  audioVideo,
  computer,
  health,
  imaging,
  misc,
  networking,
  peripheral,
  phone,
  toy,
  uncategorized,
  wearable,
}

ReportType

enum ReportType {
  input,    // Input report
  output,   // Output report
  feature,  // Feature report
}

Platform-Specific Setup

Android

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.

iOS / macOS

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.

Windows / Linux

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:
    - bluez

Platform-Specific APIs

iOS-Only APIs

The following APIs are only available on iOS:

  • closeEASession([String? protocolString]) - Closes an External Accessory session
  • accessoryConnected callback - Triggered when an iOS External Accessory is connected
  • accessoryDisconnected callback - 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.

HID APIs (Not Available on iOS or Linux)

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 device
  • sendReport(String deviceId, Uint8List data) - Send HID report
  • setupSdp({required SdpConfig config}) - Setup SDP service
  • closeSdp() - Close SDP service
  • onGetReport callback - Handle HID get report requests
  • onSdpServiceRegistrationUpdate callback - SDP registration updates
  • onConnectionStateChanged callback - 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.

Linux Limitations

On Linux, the following APIs are not implemented:

  • showBluetoothAccessoryPicker() - Native picker not available
  • connect() - HID connection not supported
  • sendReport() - HID reports not supported
  • setupSdp() - SDP service not supported
  • closeSdp() - SDP service not supported

Available on Linux:

  • startScan(), stopScan(), isScanning()
  • pair(), unpair()
  • getPairedDevices()
  • disconnect() (basic disconnect only)
  • onBluetoothDeviceDiscover, onBluetoothDeviceRemoved callbacks

Customizing Platform Implementation

// 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());

🧩 Apps using Flutter Accessory Manager

Here are some of the apps leveraging the power of flutter_accessory_manager in production:

BT Cam Icon 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!

About

Flutter implementation of iOS AccessoryManager

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

 

Packages

No packages published

Contributors 3

  •  
  •  
  •