When performing rapid consecutive writeWithoutResponse operations on iOS, packets are silently dropped by the CoreBluetooth BLE stack. This is because the current implementation calls peripheral.writeValue(_:for:type:.withoutResponse) and returns immediately without waiting for the internal BLE buffer to be ready for the next write.
iOS provides two mechanisms to prevent this:
CBPeripheral.canSendWriteWithoutResponse — a property indicating whether the peripheral is ready to accept another write-without-response
peripheralIsReadyToSendWriteWithoutResponse(_:) — a CBPeripheralDelegate callback fired when the peripheral's buffer has space again
Neither is currently used in reactive_ble_mobile.
Current behavior
// Central.swift - writeWithoutResponse
func writeWithoutResponse(value: Data, characteristic: CharacteristicInstance) throws {
// ...
peripheral.writeValue(value, for: characteristic, type: .withoutResponse)
// Returns immediately, no flow control
}
No check on canSendWriteWithoutResponse before writing
peripheralIsReadyToSendWriteWithoutResponse is not implemented in PeripheralDelegate
Rapid writes overflow the iOS BLE buffer → silent packet loss
Developers must manually add arbitrary delays between writes, which is fragile and device-dependent
Expected behavior
The plugin should:
Check peripheral.canSendWriteWithoutResponse before each write-without-response
Implement the peripheralIsReadyToSendWriteWithoutResponse(_:) delegate callback
Either queue writes internally or signal completion to the Dart layer only after the packet has been accepted by the BLE stack
This ensures the Dart-side Future (or equivalent) doesn't complete until the data has actually been dispatched, preventing buffer overflows without requiring manual delays.
Reference implementation
flutter_blue_plus (v1.36.8) handles this correctly in its darwin plugin:
Checks canSendWriteWithoutResponse before writing; returns an error if false
Implements peripheralIsReadyToSendWriteWithoutResponse to notify the Dart layer that the write has been dispatched
The Dart layer awaits this signal before allowing the next write operation
Impact
This issue affects any use case involving high-throughput write-without-response, such as:
OTA firmware updates
Streaming sensor configuration
Bulk data transfer to BLE peripherals
LED/lighting control with rapid color changes
Environment
iOS 13+
reactive_ble_mobile: 5.4.2
Affected only on iOS/macOS (Android has its own flow control via onCharacteristicWrite callback)
When performing rapid consecutive writeWithoutResponse operations on iOS, packets are silently dropped by the CoreBluetooth BLE stack. This is because the current implementation calls peripheral.writeValue(_:for:type:.withoutResponse) and returns immediately without waiting for the internal BLE buffer to be ready for the next write.
iOS provides two mechanisms to prevent this:
CBPeripheral.canSendWriteWithoutResponse — a property indicating whether the peripheral is ready to accept another write-without-response
peripheralIsReadyToSendWriteWithoutResponse(_:) — a CBPeripheralDelegate callback fired when the peripheral's buffer has space again
Neither is currently used in reactive_ble_mobile.
Current behavior
// Central.swift - writeWithoutResponse
func writeWithoutResponse(value: Data, characteristic: CharacteristicInstance) throws {
// ...
peripheral.writeValue(value, for: characteristic, type: .withoutResponse)
// Returns immediately, no flow control
}
No check on canSendWriteWithoutResponse before writing
peripheralIsReadyToSendWriteWithoutResponse is not implemented in PeripheralDelegate
Rapid writes overflow the iOS BLE buffer → silent packet loss
Developers must manually add arbitrary delays between writes, which is fragile and device-dependent
Expected behavior
The plugin should:
Check peripheral.canSendWriteWithoutResponse before each write-without-response
Implement the peripheralIsReadyToSendWriteWithoutResponse(_:) delegate callback
Either queue writes internally or signal completion to the Dart layer only after the packet has been accepted by the BLE stack
This ensures the Dart-side Future (or equivalent) doesn't complete until the data has actually been dispatched, preventing buffer overflows without requiring manual delays.
Reference implementation
flutter_blue_plus (v1.36.8) handles this correctly in its darwin plugin:
Checks canSendWriteWithoutResponse before writing; returns an error if false
Implements peripheralIsReadyToSendWriteWithoutResponse to notify the Dart layer that the write has been dispatched
The Dart layer awaits this signal before allowing the next write operation
Impact
This issue affects any use case involving high-throughput write-without-response, such as:
OTA firmware updates
Streaming sensor configuration
Bulk data transfer to BLE peripherals
LED/lighting control with rapid color changes
Environment
iOS 13+
reactive_ble_mobile: 5.4.2
Affected only on iOS/macOS (Android has its own flow control via onCharacteristicWrite callback)