Skip to content

Flutter plugin for connecting and communicationg with Bluetooth Low Energy devices, on Android and iOS

License

Notifications You must be signed in to change notification settings

Musma/flutter_blue_plus

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

pub package Chat


FlutterBlue



Note: this plugin is continuous work from FlutterBlue.

Migrating from FlutterBlue? See Migration Guides

Contents

Introduction

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.

❗ Bluetooth Classic is not supported ❗

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.

Cross-Platform Bluetooth Low Energy

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.

No Dependencies

FlutterBluePlus has zero dependencies besides Flutter, Android, and iOS themselves.

This makes FlutterBluePlus very stable, and easy to maintain.

⭐ Stars ⭐

Please star this repo & on pub.dev. We all benefit from having a larger community.

Example

FlutterBluePlus has a beautiful example app, useful to debug issues.

cd ./example
flutter run

FlutterBlue

Usage

πŸ”₯ Error Handling πŸ”₯

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(...). The one exception is FlutterBluePlus.scanResults, which you should handle onError.


Set Log Level

// 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

Screenshot 2023-07-27 at 4 53 08 AM

Enable Bluetooth

Note: On iOS, a "This app would like to use Bluetooth" system dialogue appears on first call to any FlutterBluePlus method.

// check if bluetooth is supported by your hardware
// Note: The platform is initialized on the first call to any FlutterBluePlus method.
if (await FlutterBluePlus.isSupported == false) {
    print("Bluetooth not supported by this device");
    return;
}

// handle bluetooth on & off
// note: for iOS the initial state is typically BluetoothAdapterState.unknown
// note: if you have permissions issues you will get stuck at BluetoothAdapterState.unauthorized
FlutterBluePlus.adapterState.listen((BluetoothAdapterState state) {
    print(state);
    if (state == BluetoothAdapterState.on) {
        // usually start scanning, connecting, etc
    } else {
        // show an error to the user, etc
    }
});

// turn on bluetooth ourself if we can
// for iOS, the user controls bluetooth enable/disable
if (Platform.isAndroid) {
    await FlutterBluePlus.turnOn();
}

Scan for devices

If your device is not found, see Common Problems.

// Setup Listener for scan results.
// device not found? see "Common Problems" in the README
Set<DeviceIdentifier> seen = {};
var subscription = FlutterBluePlus.scanResults.listen(
    (results) {
        for (ScanResult r in results) {
            if (seen.contains(r.device.remoteId) == false) {
                print('${r.device.remoteId}: "${r.advertisementData.localName}" found! rssi: ${r.rssi}');
                seen.add(r.device.remoteId);
            }
        }
    },
    onError(e) => print(e);
);

// Start scanning
await FlutterBluePlus.startScan();

// Stop scanning
await FlutterBluePlus.stopScan();

Connect to a device

// listen for disconnection
device.connectionState.listen((BluetoothConnectionState state) async {
    if (state == BluetoothConnectionState.disconnected) {
        // 1. typically, start a periodic timer that tries to 
        //    periodically reconnect, or just call connect() again right now
        // 2. you must always re-discover services after disconnection!
    }
});

// Connect to the device
await device.connect();

// Disconnect from device
await device.disconnect();

Get MTU and request larger size

final mtuSubscription = device.mtu.listen((int mtu) {
    // iOS: initial value is always 23, but iOS will quickly negotiate a higher value
    // android: you must request higher mtu yourself
    print("mtu $mtu");
});

// cleanup: cancel subscription when disconnected
device.cancelWhenDisconnected(mtuSubscription);

// Very important!
if (Platform.isAndroid) {
    await device.requestMtu(512);
}

Discover services

// Note: You must call discoverServices after every re-connection!
List<BluetoothService> services = await device.discoverServices();
services.forEach((service) {
    // do something with service
});

Read Characteristics

// Reads all characteristics
var characteristics = service.characteristics;
for(BluetoothCharacteristic c in characteristics) {
    if (c.properties.read) {
        List<int> value = await c.read();
        print(value);
    }
}

Write Characteristic

// 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 be designed to support split data
extension splitWrite on BluetoothCharacteristic {
  Future<void> splitWrite(List<int> value, {int timeout = 15}) async {
    int chunk = (await device.mtu.first) - 3; // 3 bytes ble overhead
    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);
    }
  }
}

Read and write descriptors

// 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])

Set notifications and listen to changes

If onValueReceived is never called, see Common Problems in the README.

final chrSubscription = characteristic.onValueReceived.listen((value) {
    // onValueReceived is updated:
    //   - anytime read() is called
    //   - anytime a notification arrives (if subscribed)
});

// cleanup: cancel subscription when disconnected
device.cancelWhenDisconnected(chrSubscription);

// enable notifications
await characteristic.setNotifyValue(true);

Last Value Stream

lastValueStream is an alternative to onValueReceived. It emits a value any time the characteristic changes, including writes.

It is very convenient for simple characteristics that support both WRITE and READ (and/or NOTIFY). e.g. a "light switch toggle" characteristic.

final chrSubscription = characteristic.lastValueStream.listen((value) {
    //lastValueStream` is updated:
    //   - anytime read() is called
    //   - anytime write() is called
    //   - anytime a notification arrives (if subscribed)
    //   - also when first listened to, it re-emits the last value for convenience.
});

// cleanup: cancel subscription when disconnected
device.cancelWhenDisconnected(chrSubscription);

// enable notifications
await characteristic.setNotifyValue(true);

Get Connected Devices

Get devices currently connected to your app.

List<BluetoothDevice> devs = FlutterBluePlus.connectedDevices;
for (var d in devs) {
    print(d);
}

Get System Devices

Get devices connected to the system by any app.

Note: you must connect your app to them before you can communicate with them.

List<BluetoothDevice> devs = await FlutterBluePlus.systemDevices;
for (var d in devs) {
    await d.connect(); // Must connect *our* app to the device
    await d.discoverServices();
}

Create Bond (Android Only)

Note: calling this is usually not necessary!! The platform will do it automatically.

However, you can force the popup to show sooner.

    final bsSubscription = device.bondState.listen((value) {
        print("$value prev:{$device.prevBondState}");
    });

    // cleanup: cancel subscription when disconnected
    device.cancelWhenDisconnected(bsSubscription);

    // Force the bonding popup to show now (Android Only) 
    await device.createBond();

    // remove bond
    await device.removeBond();

Events API

Access streams from all devices simultaneously.

There are streams for:

  • events.connectionState
  • events.onCharacteristicReceived
  • events.onDescriptorRead
  • events.onNameChanged
  • events.onServicesChanged
  • events.bondState
// listen to *any device* connection state changes 
FlutterBluePlus.events.connectionState.listen((event)) {
    print('${event.device} ${event.connectionState}');
}

Getting Started

Change the minSdkVersion for Android

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

Add permissions for Android (No 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" 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" />

Add permissions for Android (With Fine Location)

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

Android Proguard

Add the following line in your project/android/app/proguard-rules.pro file:

-keep class com.lib.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

Add permissions for iOS

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

Mocking

To mock FlutterBluePlus for development, refer to the Mocking Guide.

Reference

πŸŒ€ = Stream

FlutterBluePlus API

Android iOS Throws Description
isSupported βœ… βœ… Checks whether the device supports Bluetooth
turnOn βœ… πŸ”₯ Turns on the bluetooth adapter
adapterState πŸŒ€ βœ… βœ… Stream of on & off states of the bluetooth adapter
startScan βœ… βœ… πŸ”₯ Starts a scan for Ble devices
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?
connectedDevices βœ… βœ… List of devices connected to your app
systemDevices βœ… βœ… List of devices connected to the system, even by other apps
setLogLevel βœ… βœ… Configure plugin log level
getPhySupport βœ… πŸ”₯ Get supported bluetooth phy codings

FlutterBluePlus Events API

Android iOS Throws Description
events.connectionState πŸŒ€ βœ… βœ… Stream of connection changes of all devices
events.onCharacteristicReceived πŸŒ€ βœ… βœ… Stream of characteristic value reads of all devices
events.onDescriptorRead πŸŒ€ βœ… βœ… Stream of descriptor value reads of all devices
events.onNameChanged πŸŒ€ βœ… Stream of name changes of all devices
events.onServicesChanged πŸŒ€ βœ… Stream of services changes of all devices
events.bondState πŸŒ€ βœ… Stream of bondState changes of all devices

BluetoothDevice API

Android iOS Throws Description
platformName βœ… βœ… The platform cached name of the device
connect βœ… βœ… πŸ”₯ Establishes a connection to the device
disconnect βœ… βœ… πŸ”₯ Cancels an active or pending connection to the device
isConnected βœ… βœ… Is this device currently connected to your app?
connectionState πŸŒ€ βœ… βœ… Stream of connection changes for the Bluetooth Device
discoverServices βœ… βœ… πŸ”₯ Discover services
servicesList βœ… βœ… The list of services that were discovered
onServicesChanged πŸŒ€ βœ… βœ… The services changed & must be rediscovered
onNameChanged πŸŒ€ βœ… βœ… The GAP Device Name Characteristic (0x2A00) changed
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
bondState πŸŒ€ βœ… Stream of device bond state. Can be useful on Android
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

BluetoothCharacteristic API

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 onValueReceived + writes

BluetoothDescriptor API

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 onValueReceived + writes

Debugging

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.

Common Problems

Many common problems are easily solved.


Scanning does not find my device

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 system devices:

// search system devices. i.e. any device connected to by *any* app
List<BluetoothDevice> system = await FlutterBluePlus.systemDevices;
for (var d in system) {
    print('${r.device.platformName} already connected to! ${r.device.remoteId}');
    if (d.platformName == "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?


Connection fails

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.


onValueReceived (or lastValueStream) is never called

1. you are not calling the right function

lastValueStream is called for await chr.read() & await chr.write() & await chr.setNotifyValue(true)

onValueReceived is only called for await chr.read() & await chr.setNotifyValue(true)

2. your device has nothing to send

If you are using await chr.setNotifyValue(true), your device chooses when to send data.

Try interacting with your device to get it to send new data.

3. your device has bugs

Try rebooting your ble device.

Some ble devices have buggy software and stop sending data


onValueReceived (or lastValueStream) data is split up

You are probably forgetting to increase the android mtu.

if (Platform.isAndroid) {
    await device.requestMtu(512);
}

Verify that the mtu is large enough to hold your message.

device.mtu

If it still happens, it is a problem with your peripheral device.


onValueReceived (or lastValueStream) is called with duplicate data

You are probably forgetting to cancel the original stream.listen resulting in multiple listens.

The easiest solution is to use device.cancelWhenDisconnected(subscription) to cancel device subscriptions.

final subscription = characteristic.onValueReceived.listen((value) {
    // ...
});

// make sure you have this line!
device.cancelWhenDisconnected(subscription);

await characteristic.setNotifyValue(true);

characteristic writes fails

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 (with response): look in the Usage section for functions 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.


characteristic read fails

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.

About

Flutter plugin for connecting and communicationg with Bluetooth Low Energy devices, on Android and iOS

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Dart 42.9%
  • Java 32.5%
  • Objective-C 22.9%
  • Ruby 1.3%
  • Swift 0.4%