core_bluetooth

A clean Dart wrapper around Apple's CoreBluetooth framework for iOS and macOS.

Both central mode and peripheral mode are supported.

Status

✅ Fully implemented.

Apple CoreBluetooth symbol names are the primary public API. Helper wrapper types exist where Dart needs them, but they intentionally do not use the CB prefix.

Example

The package includes a working example app:

cd ./example
flutter run

Usage - Central Mode

For connecting to devices.

Initialize Central Manager

Use the shared app-wide manager:

final central = CBCentralManager.shared;

Or configure the shared manager before first use:

CBCentralManager.ensureShared(
  options: const CentralManagerOptions(
    showPowerAlert: true,
    restoreIdentifier: "my_restore_id",
  ),
);

final central = CBCentralManager.shared;

Or create an isolated manager:

final central = CBCentralManager.createIsolated(
  options: const CentralManagerOptions(
    showPowerAlert: true,
    restoreIdentifier: "my_restore_id",
  ),
);

Bluetooth On & Off

final central = CBCentralManager.shared;

final subscription = central.onDidUpdateState.listen((state) {
  print(state);
  if (state == CBManagerState.poweredOn) {
    // start scanning, connecting, etc
  }
});

Scan for Devices

final subscription = central.onDidDiscoverPeripheral.listen((result) {
  print(result.peripheral.identifier.uuidString);
  print(result.advertisementData.localName);
  print(result.rssi);
});

await central.scanForPeripherals(
  withServices: [CBUUID("180D")],
  options: const CentralManagerScanOptions(
    allowDuplicates: true,
  ),
);

await central.stopScan();

Connect to a Device

await central.connect(peripheral);
await central.cancelPeripheralConnection(peripheral);

Discover Services

final subscription = peripheral.onDidDiscoverServices.listen((result) {
  print(result.services);
});

await peripheral.discoverServices();

Iterate Services & Characteristics

for (final service in peripheral.services ?? const <CBService>[]) {
  print('service: ${service.uuid.uuidString}');

  for (final characteristic in service.characteristics ?? const <CBCharacteristic>[]) {
    print('  characteristic: ${characteristic.uuid.uuidString}');

    for (final descriptor in characteristic.descriptors ?? const <CBDescriptor>[]) {
      print('    descriptor: ${descriptor.uuid.uuidString}');
    }
  }
}

Read, Write, and Notify

await peripheral.readValue(characteristic);

await peripheral.writeValue(
  Uint8List.fromList([0x01, 0x02]),
  forAttribute: characteristic,
  type: CBCharacteristicWriteType.withResponse,
);

await peripheral.setNotifyValue(true, characteristic);

final subscription = peripheral.onDidUpdateValueForCharacteristic.listen((result) {
  print(result.characteristic.value);
});

L2CAP

final subscription = peripheral.onDidOpenL2CAPChannel.listen((result) async {
  final channel = result.channel;
  if (channel == null) {
    return;
  }

  final incoming = await channel.inputStream.read();
  print(incoming);

  await channel.outputStream.write(Uint8List.fromList([0x01, 0x02]));
});

await peripheral.openL2CAPChannel(1234);

Usage - Peripheral Mode

For advertising yourself as a device.

Initialize Peripheral Manager

Use the shared app-wide manager:

final peripheralManager = CBPeripheralManager.shared;

Or configure the shared manager before first use:

CBPeripheralManager.ensureShared(
  options: const PeripheralManagerOptions(
    showPowerAlert: true,
    restoreIdentifier: "my_restore_id",
  ),
);

final peripheralManager = CBPeripheralManager.shared;

Or create an isolated manager:

final peripheralManager = CBPeripheralManager.createIsolated(
  options: const PeripheralManagerOptions(
    showPowerAlert: true,
    restoreIdentifier: "my_restore_id",
  ),
);

Start Advertising

final peripheralManager = CBPeripheralManager.shared;

final service = CBMutableService.type(
  type: CBUUID("1234"),
  primary: true,
);

final characteristic = CBMutableCharacteristic.type(
  type: CBUUID("ABCD"),
  properties: const CBCharacteristicProperties(
    CBCharacteristicProperties.read.rawValue | CBCharacteristicProperties.notify.rawValue,
  ),
  value: null,
  permissions: CBAttributePermissions.readable,
);

service.characteristics = [characteristic];

await peripheralManager.add(service);

await peripheralManager.startAdvertising(
  const PeripheralManagerAdvertisingData(
    localName: "core_bluetooth",
    serviceUUIDs: [CBUUID("1234")],
  ),
);

Peripheral Read / Write Requests

final readSubscription = peripheralManager.onDidReceiveReadRequest.listen((result) async {
  final request = result.request;

  request.value = Uint8List.fromList([0x01, 0x02, 0x03]);

  await peripheralManager.respond(
    to: request,
    withResult: CBATTErrorCode.success,
  );
});

final writeSubscription = peripheralManager.onDidReceiveWriteRequests.listen((result) async {
  for (final request in result.requests) {
    print(request.value);
  }
});

Update Subscribed Centrals

final didSend = await peripheralManager.updateValue(
  Uint8List.fromList([0x10, 0x20]),
  forCharacteristic: characteristic,
  onSubscribedCentrals: null,
);

print(didSend);

Peripheral L2CAP

final publishSubscription = peripheralManager.onDidPublishL2CAPChannel.listen((result) {
  print(result.psm);
});

final openSubscription = peripheralManager.onDidOpenL2CAPChannel.listen((result) async {
  final channel = result.channel;
  if (channel == null) {
    return;
  }

  await channel.outputStream.write(Uint8List.fromList([0x10, 0x20]));
});

await peripheralManager.publishL2CAPChannel(withEncryption: false);

Contract

Below is the CoreBluetooth API map we should treat as the contract.

Legend:

  • fully implemented

Top-Level Framework

  • CBManager
  • CBPeer
  • CBUUID
  • CBError
  • CBATTRequest
  • CBL2CAPChannel
  • CBL2CAPPSM

Central Role

  • CBCentralManager
  • CBCentralManagerDelegate
  • CBCentral
  • CBConnectionEvent

CBCentralManager

  • shared
  • ensureShared(options: ...)
  • createIsolated()
  • var delegate
  • var isScanning
  • class func supports(_ feature: CBCentralManager.Feature) -> Bool
  • struct CBCentralManager.Feature
  • func scanForPeripherals(withServices:options:)
  • func stopScan()
  • func retrievePeripherals(withIdentifiers:)
  • func retrieveConnectedPeripherals(withServices:)
  • func connect(_:options:)
  • func cancelPeripheralConnection(_:)
  • func registerForConnectionEvents(options:)
  • class var authorization
  • inherited from CBManager: var state

CBCentralManagerDelegate

  • centralManagerDidUpdateState(_:)
  • centralManager(_:willRestoreState:)
  • centralManager(_:didDiscover:advertisementData:rssi:)
  • centralManager(_:didConnect:)
  • centralManager(_:didFailToConnect:error:)
  • centralManager(_:didDisconnectPeripheral:error:)
  • centralManager(_:didDisconnectPeripheral:timestamp:isReconnecting:error:)
  • centralManager(_:connectionEventDidOccur:for:)
  • centralManager(_:didUpdateANCSAuthorizationFor:)

CBCentral

  • inherits CBPeer
  • var maximumUpdateValueLength

Peripheral Role

  • CBPeripheral
  • CBPeripheralDelegate
  • CBPeripheralManager
  • CBPeripheralManagerDelegate

CBPeripheral

  • var delegate
  • var name
  • var identifier
  • var state
  • var services
  • var ancsAuthorized
  • var canSendWriteWithoutResponse
  • func readRSSI()
  • func discoverServices(_:)
  • func discoverIncludedServices(_:for:)
  • func discoverCharacteristics(_:for:)
  • func discoverDescriptors(for:)
  • func readValue(for: CBCharacteristic)
  • func readValue(for: CBDescriptor)
  • func writeValue(_:for: CBCharacteristic,type:)
  • func writeValue(_:for: CBDescriptor)
  • func setNotifyValue(_:for:)
  • func maximumWriteValueLength(for:)
  • func openL2CAPChannel(_:)

CBPeripheralDelegate

  • peripheralDidUpdateName(_:)
  • peripheral(_:didModifyServices:)
  • peripheral(_:didReadRSSI:error:)
  • peripheral(_:didDiscoverServices:)
  • peripheral(_:didDiscoverIncludedServicesFor:error:)
  • peripheral(_:didDiscoverCharacteristicsFor:error:)
  • peripheral(_:didDiscoverDescriptorsFor:error:)
  • peripheral(_:didUpdateValueFor:error:) for CBCharacteristic
  • peripheral(_:didUpdateValueFor:error:) for CBDescriptor
  • peripheral(_:didWriteValueFor:error:) for CBCharacteristic
  • peripheral(_:didWriteValueFor:error:) for CBDescriptor
  • peripheral(_:didUpdateNotificationStateFor:error:)
  • peripheralIsReady(toSendWriteWithoutResponse:)
  • peripheral(_:didOpen:error:)

CBPeripheralManager

  • shared
  • ensureShared(options: ...)
  • createIsolated()
  • init()
  • init(delegate:queue:)
  • init(delegate:queue:options:)
  • var delegate
  • var isAdvertising
  • func add(_:)
  • func remove(_:)
  • func removeAllServices()
  • func startAdvertising(_:)
  • func stopAdvertising()
  • func updateValue(_:for:onSubscribedCentrals:) -> Bool
  • func respond(to:withResult:)
  • func setDesiredConnectionLatency(_:for:)
  • func publishL2CAPChannel(withEncryption:)
  • func unpublishL2CAPChannel(_:)
  • class var authorization
  • inherited from CBManager: var state

CBPeripheralManagerDelegate

  • peripheralManagerDidUpdateState(_:)
  • peripheralManager(_:willRestoreState:)
  • peripheralManagerDidStartAdvertising(_:error:)
  • peripheralManager(_:didAdd:error:)
  • peripheralManager(_:central:didSubscribeTo:)
  • peripheralManager(_:central:didUnsubscribeFrom:)
  • peripheralManagerIsReady(toUpdateSubscribers:)
  • peripheralManager(_:didReceiveRead:)
  • peripheralManager(_:didReceiveWrite:)
  • peripheralManager(_:didPublishL2CAPChannel:error:)
  • peripheralManager(_:didUnpublishL2CAPChannel:error:)
  • peripheralManager(_:didOpen:error:)

GATT Model Types

  • CBAttribute
  • CBService
  • CBMutableService
  • CBCharacteristic
  • CBMutableCharacteristic
  • CBDescriptor
  • CBMutableDescriptor

CBAttribute

  • var uuid

CBService

  • var peripheral
  • var isPrimary
  • var includedServices
  • var characteristics

CBMutableService

  • writable versions of service properties

CBCharacteristic

  • var service
  • var value
  • var descriptors
  • var properties
  • var isNotifying
  • var isBroadcasted

CBMutableCharacteristic

  • init(type:properties:value:permissions:)

CBDescriptor

  • var characteristic
  • var value

CBMutableDescriptor

  • init(type:value:)

Supporting Enums / Option Sets / Constants

  • CBManagerState
  • CBManagerAuthorization
  • CBPeripheralState
  • CBCharacteristicWriteType
  • CBCentralManager.Feature
  • CBCharacteristicProperties
  • CBAttributePermissions
  • CBPeripheralManagerConnectionLatency
  • CBATTError.Code
  • advertisement-data keys
  • peripheral-scanning option keys
  • peripheral-connection option keys
  • peripheral-manager initialization option keys
  • peripheral-manager restoration keys
  • central-manager restoration keys
  • connection-event matching option keys

Libraries

core_bluetooth