Breaking changes in FlutterBluePlus, listed version by version.
- renamed:
BluetoothDevice.id->remoteId - renamed:
FlutterBluePlus.name->adapterName - renamed:
BluetoothDevice.name->platformName - renamed:
FlutterBluePlus.state->adapterState - renamed:
BluetoothDevice.state->connectionState
- Behavior Change: Android: push to
onValueReceivedwhenread()is called, to match iOS behavior - renamed:
BluetoothCharacteristic.value->lastValueStream - renamed:
BluetoothDescriptor.value->lastValueStream - renamed:
BluetoothCharacteristic.onValueChangedStream->onValueReceived - renamed:
BluetoothDescriptor.onValueChangedStream->onValueReceived
You no longer need to use .instance
i.e. FlutterBluePlus.instance.startScan becomes FlutterBluePlus.startScan
- they now wait for completion if you use
await - they throw on error, instead of returning false
- renamed:
connectedDevices->connectedSystemDevices
Option 1: migrate to FlutterBluePlus.startScan with oneByOne parameter
Option 2: use the following extension (below)
extension Scan on FlutterBluePlus {
static Stream<ScanResult> scan({
List<Guid> withServices = const [],
Duration? timeout,
bool androidUsesFineLocation = false,
}) {
if (FlutterBluePlus.isScanningNow) {
throw Exception("Another scan is already in progress");
}
final controller = StreamController<ScanResult>();
var subscription = FlutterBluePlus.scanResults.listen(
(r){if(r.isNotEmpty){controller.add(r.first);}},
onError: (e, stackTrace) => controller.addError(e, stackTrace),
);
Future scanComplete = FlutterBluePlus.isScanning.skip(1).where((e) => e == false).first;
FlutterBluePlus.startScan(
withServices: withServices,
timeout: timeout,
removeIfGone: null,
oneByOne: true,
androidUsesFineLocation: androidUsesFineLocation,
);
scanComplete.whenComplete(() {
subscription.cancel();
controller.close();
});
return controller.stream;
}
}
Option 1: migrate to FlutterBluePlus.scanResults. Example code:
Stream<BluetoothDevice?> myDeviceStream = FlutterBluePlus.scanResults
.map((list) => list.first)
.where((r) => r.advertisementData.advName == "myDeviceName")
.map((r) => r.device);
// start listening before we call startScan so we do not miss the result
Future<BluetoothDevice?> myDeviceFuture = myDeviceStream.first
.timeout(Duration(seconds: 10))
.catchError((error) => null);
await FlutterBluePlus.startScan(timeout: Duration(seconds: 10), oneByOne:true);
BluetoothDevice? myDevice = await myDeviceFuture;
Option 2: use this extension
extension Scan on FlutterBluePlus {
static Future<List<ScanResult>> startScanWithResult({
List<Guid> withServices = const [],
Duration? timeout,
bool androidUsesFineLocation = false,
}) async {
if (FlutterBluePlus.isScanningNow) {
throw Exception("Another scan is already in progress");
}
List<ScanResult> output = [];
var subscription = FlutterBluePlus.scanResults.listen((result) {
output = result;
}, onError: (e, stackTrace) {
throw Exception(e);
});
FlutterBluePlus.startScan(
withServices: withServices,
timeout: timeout,
removeIfGone: null,
oneByOne: false,
androidUsesFineLocation: androidUsesFineLocation,
);
// wait scan complete
await FlutterBluePlus.isScanning.where((e) => e == false).first;
subscription.cancel();
return output;
}
}
Use isScanning to detect completion instead.
await FlutterBluePlus.startScan(timeout: Duration(seconds:15));
await FlutterBluePlus.isScanning.where((value) => value == false).first;
- renamed:
BluetoothDevice.localName->platformName - deleted:
BluetoothDevice.type&BluetoothDevice.localNamefrom constructor - deleted:
servicesStream&isDiscoveringServices
- Behavior Change:
lastValue&lastValueStreamare now updated whenwrite()is called
- Breaking Change bondState: directly expose prevBond instead of lost/failed flags
- renamed:
connectedSystemDevices->systemDevices, because they must be re-connected by your app.
Caution: this release introduces a new function called connectedDevices. Before 1.11.0, there used to be a function with this same name. That older function has since been renamed to systemDevices.
- Behavior Change: only allow a single ble operation at a time.
This change was made to increase reliability, at the cost of throughput.
- Breaking Change: on android, we now request an mtu of 512 by default during connection.
These releases changed multiple things but then changed them back. For brevity, here are the actual changes:
- [Behavior Change] android: always listen to Services Changed characteristic, to match iOS behavior
- [Rename]
device.onServicesChanged->device.onServicesReset - [Remove]
device.onNameChanged, in favor of only exposingevents.onNameChanged - [Rename] events api: most functions & classes were renamed for consistency
- [Breaking Change] scanning:
continousUpdatesis now false by default - it is not typically needed & hurts perf.
If your app uses startScan.removeIfGone, or your app continually checks the value of scanResult.timestamp or scanResult.rssi, then you will need to explicitly set continousUpdates to true.
- [Rename]
advertisementData.localName->advertisementData.advName
- [Breaking Change]
guid.toString()now returns 16-bit short uuid when possibe - [Breaking Change] use GUID for
advertisingData.serviceUuids&advertisingData.serviceDatainstead of String
- [Breaking Change] scanResults: do not clear results after
stopScan. If you want results cleared, useonScanResultsinstead.