Note: this plugin is continuous work from FlutterBlue since maintenance stopped.
FlutterBluePlus is a Bluetooth Low Energy plugin for Flutter.
It supports BLE Central Role only (most common).
If you need BLE Peripheral Role, you should check out FlutterBlePeripheral.
i.e. speakers, headphones, mice, keyboards, gamepads, Arduino HC-05 & HC-06, and more are not supported. These all use Bluetooth Classic.
Also, iBeacons are not supported on iOS. Apple requires you to use CoreLocation.
FlutterBluePlus aims to offer the most from all supported platforms: iOS, macOS, Android.
The code is written to be simple, robust, and incredibly easy to understand.
FlutterBluePlus has zero dependencies besides Flutter, Android, and iOS themselves.
This makes FlutterBluePlus very stable, and easy to maintain.
Please star this repo & on pub.dev. We all benefit from having a larger community.
Flutter Blue Plus takes error handling very seriously.
Every error returned by the native platform is checked and thrown as an exception where appropriate. See Reference for a list of throwable functions.
Streams: At the time of writing, streams returned by Flutter Blue Plus never emit any errors and never close. There's no need to handle onError
or onDone
for stream.listen(...)
.
// if your terminal doesn't support color you'll see annoying logs like `\x1B[1;35m`
FlutterBluePlus.setLogLevel(LogLevel.verbose, color:false)
Setting LogLevel.verbose
shows all data in and out.
β« = function name
π£ = args to platform
π‘ = data from platform
Note: On iOS, a "This app would like to use Bluetooth" system dialogue appears on first call to a FlutterBluePlus method. This is when the CBCentralManager
(iOS) & BluetoothManager
(Android) are initialized.
// check adapter availability
if (await FlutterBluePlus.isAvailable == false) {
print("Bluetooth not supported by this device");
return;
}
// turn on bluetooth ourself if we can
// for iOS, the user controls bluetooth enable/disable
if (Platform.isAndroid) {
await FlutterBluePlus.turnOn();
}
// wait bluetooth to be on & print states
// note: for iOS the initial state is typically BluetoothAdapterState.unknown
// note: if you have permissions issues you will get stuck at BluetoothAdapterState.unauthorized
await FlutterBluePlus.adapterState
.map((s){print(s);return s;})
.where((s) => s == BluetoothAdapterState.on)
.first;
If your device is not found, see Common Problems.
// Setup Listener for scan results
// device not found? see "Common Problems" in the README
var subscription = FlutterBluePlus.scanResults.listen((results) {
for (ScanResult r in results) {
print('${r.device.localName} found! rssi: ${r.rssi}');
}
});
// Start scanning
FlutterBluePlus.startScan(timeout: Duration(seconds: 4));
// Stop scanning
await FlutterBluePlus.stopScan();
// listen for disconnection
device.connectionState.listen((BluetoothConnectionState state) async {
if (state == BluetoothConnectionState.disconnected) {
// typically, start a periodic timer that tries to periodically reconnect.
// Note: you must always re-discover services after disconnection!
}
});
// Connect to the device
await device.connect();
// Disconnect from device
await device.disconnect();
// Note: You must call this again if disconnected!
List<BluetoothService> services = await device.discoverServices();
services.forEach((service) {
// do something with service
});
// Reads all characteristics
var characteristics = service.characteristics;
for(BluetoothCharacteristic c in characteristics) {
List<int> value = await c.read();
print(value);
}
// Writes to a characteristic
await c.write([0x12, 0x34]);
allowLongWrite: To write large characteristics (up to 512 bytes) regardless of mtu, use allowLongWrite
:
/// allowLongWrite should be used with caution.
/// 1. it can only be used *with* response to avoid data loss
/// 2. the peripheral device must support the 'long write' ble protocol.
/// 3. Interrupted transfers can leave the characteristic in a partially written state
/// 4. If the mtu is small, it is very very slow.
await c.write(data, allowLongWrite:true);
splitWrite: To write lots of data (unlimited), you can define the splitWrite
function.
import 'dart:math';
// writeSplit should be used with caution.
// 1. due to splitting, `characteristic.read()` will return partial data.
// 2. it can only be used *with* response to avoid data loss
// 3. The characteristic must support split data
extension splitWrite on BluetoothCharacteristic {
Future<void> splitWrite(List<int> value, int mtu, {int timeout = 15}) async {
int chunk = mtu-3;
for (int i = 0; i < value.length; i += chunk) {
List<int> subvalue = value.sublist(i, min(i + chunk, value.length));
await write(subvalue, withoutResponse:false, timeout: timeout);
}
}
}
// Reads all descriptors
var descriptors = characteristic.descriptors;
for(BluetoothDescriptor d in descriptors) {
List<int> value = await d.read();
print(value);
}
// Writes to a descriptor
await d.write([0x12, 0x34])
If onValueReceived is never called, see Common Problems in the README.
// Setup Listener for characteristic reads
// If this is never called, see "Common Problems" in the README
characteristic.onValueReceived.listen((value) {
// do something with new value
});
// enable notifications
await characteristic.setNotifyValue(true);
These devices are already connected to the system, but must be reconnected by your app before you can communicate with them.
List<BluetoothDevice> connectedSystemDevices = await FlutterBluePlus.connectedSystemDevices;
for (var d in connectedSystemDevices) {
await d.connect(); // Must connect *our* app to the device
await d.discoverServices();
}
final mtu = await device.mtu.first;
await device.requestMtu(512);
Note that iOS will not allow requests of MTU size, and will always try to negotiate the highest possible MTU (iOS supports up to MTU size 185)
flutter_blue_plus is compatible only from version 21 of Android SDK so you should change this in android/app/build.gradle:
Android {
defaultConfig {
minSdkVersion: 21
In the android/app/src/main/AndroidManifest.xml add:
<!-- Tell Google Play Store that your app uses Bluetooth LE
Set android:required="true" if bluetooth is necessary -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<!-- New Bluetooth permissions in Android 12
https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- legacy for Android 11 or lower -->
<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"/>
<!-- legacy for Android 9 or lower -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" />
If you want to use Bluetooth to determine location.
In the android/app/src/main/AndroidManifest.xml add:
<!-- Tell Google Play Store that your app uses Bluetooth LE
Set android:required="true" if bluetooth is necessary -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<!-- New Bluetooth permissions in Android 12
https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- legacy for Android 11 or lower -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- legacy for Android 9 or lower -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" />
And set androidUsesFineLocation when scanning:
// Start scanning
flutterBlue.startScan(timeout: Duration(seconds: 4), androidUsesFineLocation: true);
Add the following line in your project/android/app/proguard-rules.pro
file:
-keep class com.boskokg.flutter_blue_plus.* { *; }
to avoid seeing the following kind errors in your release
builds:
PlatformException(startScan, Field androidScanMode_ for m0.e0 not found. Known fields are
[private int m0.e0.q, private b3.b0$i m0.e0.r, private boolean m0.e0.s, private static final m0.e0 m0.e0.t,
private static volatile b3.a1 m0.e0.u], java.lang.RuntimeException: Field androidScanMode_ for m0.e0 not found
In the ios/Runner/Info.plist letβs add:
<dict>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Need BLE permission</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Need BLE permission</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Need Location permission</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Need Location permission</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Need Location permission</string>
For location permissions on iOS see more at: https://developer.apple.com/documentation/corelocation/requesting_authorization_for_location_services
π = Stream
Android | iOS | Throws | Description | |
---|---|---|---|---|
isAvailable | β | β | Checks whether the device supports Bluetooth | |
turnOn | β | π₯ | Turns on the bluetooth adapter | |
adapterState π | β | β | Stream of on & off states of the bluetooth adapter | |
scan | β | β | π₯ | Starts a scan for Ble devices and returns a stream |
startScan | β | β | π₯ | Starts a scan for Ble devices with no return value |
stopScan | β | β | π₯ | Stop an existing scan for Ble devices |
scanResults π | β | β | Stream of live scan results | |
isScanning π | β | β | Stream of current scanning state | |
isScanningNow | β | β | Is a scan currently running? | |
connectedSystemDevices | β | β | List of already connected devices, including by other apps | |
setLogLevel | β | β | Configure plugin log level |
Android | iOS | Throws | Description | |
---|---|---|---|---|
localName | β | β | The cached localName of the device | |
connect | β | β | π₯ | Establishes a connection to the device |
disconnect | β | β | π₯ | Cancels an active or pending connection to the device |
discoverServices | β | β | π₯ | Discover services |
isDiscoveryingServices π | β | β | Stream of whether service discovery is in progress | |
servicesList | β | β | The list of services that were discovered | |
servicesStream π | β | β | Stream of services changes | |
connectionState π | β | β | Stream of connection changes for the Bluetooth Device | |
bondState π | β | Stream of device bond state. Can be useful on Android | ||
mtu π | β | β | π₯ | Stream of mtu size changes |
readRssi | β | β | π₯ | Read RSSI from a connected device |
requestMtu | β | π₯ | Request to change the MTU for the device | |
requestConnectionPriority | β | π₯ | Request to update a high priority, low latency connection | |
createBond | β | π₯ | Force a system pairing dialogue to show, if needed | |
removeBond | β | π₯ | Remove Bluetooth Bond of device | |
setPreferredPhy | β | Set preferred RX and TX phy for connection and phy options | ||
clearGattCache | β | π₯ | Clear android cache of service discovery results |
Android | iOS | Throws | Description | |
---|---|---|---|---|
uuid | β | β | The uuid of characeristic | |
read | β | β | π₯ | Retrieves the value of the characteristic |
write | β | β | π₯ | Writes the value of the characteristic |
setNotifyValue | β | β | π₯ | Sets notifications or indications on the characteristic |
isNotifying | β | β | Are notifications or indications currently enabled | |
onValueReceived π | β | β | Stream of characteristic value updates received from the device | |
lastValue | β | β | The most recent value of the characteristic | |
lastValueStream π | β | β | Stream of lastValue + onValueReceived |
Android | iOS | Throws | Description | |
---|---|---|---|---|
uuid | β | β | The uuid of descriptor | |
read | β | β | π₯ | Retrieves the value of the descriptor |
write | β | β | π₯ | Writes the value of the descriptor |
onValueReceived π | β | β | Stream of descriptor value reads & writes | |
lastValue | β | β | The most recent value of the descriptor | |
lastValueStream π | β | β | Stream of lastValue + onValueReceived |
The easiest way to debug issues in FlutterBluePlus is to make your own local copy.
cd /user/downloads
git clone https://github.com/boskokg/flutter_blue_plus.git
then in pubspec.yaml
add the repo by path:
flutter_blue_plus:
path: /user/downloads/flutter_blue_plus
Now you can edit the FlutterBluePlus code yourself.
Many common problems are easily solved.
1. your device uses bluetooth classic, not BLE.
Headphones, speakers, keyboards, mice, gamepads, & printers all use Bluetooth Classic.
These devices may be found in System Settings, but they cannot be connected to by FlutterBluePlus. FlutterBluePlus only supports Bluetooth Low Energy.
2. your device stopped advertising.
- you might need to reboot your device
- you might need put your device in "discovery mode"
- your phone may have already connected automatically
- another app may have already connected to your device
- another phone may have already connected to your device
Try looking through already connected devices:
// search already connected devices, including devices
// connected to by other apps
List<BluetoothDevice> system = await FlutterBluePlus.connectedSystemDevices;
for (var d in system) {
print('${r.device.localName} already connected to! ${r.device.remoteId}');
if (d.localName == "myBleDevice") {
await r.connect(); // must connect our app
}
}
3. your scan filters are wrong.
- try removing all scan filters
- for
withServices
to work, your device must actively advertise the serviceUUIDs it supports
4. try a ble scanner app
Search the App Store for a BLE scanner apps and install it on your phone, and another phone.
Question 1: When the issue is happening, is your phone (the phone with your flutter app) able to scan it using the 3rd party scanner?
Question 2: When the issue is happening, is another phone able to scan it using the 3rd party scanner?
1. Your ble device may be low battery
Bluetooth can become erratic when your peripheral device is low battery.
2. Your ble device may have refused the connection or have a bug
Connection is a two-way process. Your ble device may be misconfigured.
3. You may be on the edge of the Bluetooth range.
The signal is too weak, or there are a lot of devices causing radio interference.
4. Some phones have an issue connecting while scanning.
The Huawei P8 Lite is one of the reported phones to have this issue. Try stopping your scanner before connecting.
5. Try restarting your phone
Bluetooth is a complicated system service, and can enter a bad state.
1. you are not subscribed OR not calling read
Your device will only send values after you call await characteristic.setNotifyValue(true)
, or await characteristic.read()
2. you are calling write
onValueReceived
is only called for reads & notifies.
You can do a single read with await characteristic.read(...)
3. your device has nothing to send
If you are using setNotifyValue
, your device chooses when to send data.
Try interacting with your device to get it to send new data.
4. your device has bugs
Try rebooting your ble device.
Some ble devices have buggy software and stop sending data.
1. the characeristic is not writeable
Not all characeristics support write
.
Your device must have configured this characteristic to support write
.
2. the data length is too long
Characteristics only support writes up to a certain size.
writeWithoutResponse
: you can only write up to (MTU-3) at a time. This is a BLE limitation.
write
: look in the Usage section for a writeLarge
function you can use to solve this issue.
3. the characeristic does not support writeWithoutResponse
Not all characeristics support writeWithoutResponse
.
Your device must have configured this characteristic to support writeWithoutResponse
.
4. your bluetooth device turned off, or is out of range
If your device turns off mid-write, it will cause a failure.
5. your bluetooth device has bugs
Maybe your device crashed, or is not sending a response due to software bugs.
6. there is radio interference
Bluetooth is wireless and will not always work.
1. the characeristic is not readable
Not all characeristics support read
.
Your device must have configured this characteristic to support read
.
2. your bluetooth device turned off, or is out of range
If your device turns off mid-read, it will cause a failure.
3. your bluetooth device has bugs
Maybe your device crashed, or is not sending a response due to software bugs.
4. there is radio interference
Bluetooth is wireless and will not always work.