Flutter library that handles BLE operations for multiple devices

Overview

Flutter reactive BLE library

flutter_reactive_ble version

Flutter library that handles BLE operations for multiple devices.

Usage

The reactive BLE lib supports the following:

  • BLE device discovery
  • Observe host device BLE status
  • Establishing a BLE connection
  • Maintaining connection status of multiple BLE devices
  • Discover services(will be implicit)
  • Read / write a characteristic
  • Subscribe to a characteristic
  • Clear GATT cache
  • Negotiate MTU size

Initialization

Initializing the library should be done the following:

final flutterReactiveBle = FlutterReactiveBle();

Device discovery

Discovering BLE devices should be done like this:

flutterReactiveBle.scanForDevices(withServices: [serviceId], scanMode: ScanMode.lowLatency).listen((device) {
      //code for handling results
    }, onError: () {
      //code for handling error
    });
  

The withServices parameter specifies the advertised service IDs to look for. If an empty list is passed, all the advertising devices will be reported. The parameter scanMode is only used on Android and follows the conventions described on ScanSettings Android reference page. If scanMode is omitted the balanced scan mode will be used.

Observe host device BLE status

Use statusStream to retrieve updates about the BLE status of the host device (the device running the app) . This stream can be used in order to determine if the BLE is turned on, on the device or if the required permissions are granted. Example usage:

_ble.statusStream.listen((status) {
  //code for handling status update
});

Use _ble.status to get the current status of the host device.

See BleStatus for more info about the meaning of the different statuses.

Establishing connection

To interact with a device you first need to establish a connection:

flutterReactiveBle.connectToDevice(
      id: foundDeviceId,
      servicesWithCharacteristicsToDiscover: {serviceId: [char1, char2]},
      connectionTimeout: const Duration(seconds: 2),
    ).listen((connectionState) {
      // Handle connection state updates
    }, onError: (Object error) {
      // Handle a possible error
    });

For the required id parameter use a device ID retrieved through device discovery. On iOS the device ID is a UUID and on Android it is a MAC address (which may also be randomized, depending on the Android version). Supplying a map with service and characteristic IDs you want to discover may speed up the connection on iOS (otherwise all services and characteristics will be discovered). You can specify a connectionTimeout when the client will provide an error in case the connection cannot be established within the specified time.

There are numerous issues on the Android BLE stack that leave it hanging when you try to connect to a device that is not in range. To work around this issue use the method connectToAdvertisingDevice to first scan for the device and only if it is found connect to it.

flutterReactiveBle.connectToAdvertisingDevice(
    id: foundDeviceId,
    withServices: [serviceUuid],
    prescanDuration: const Duration(seconds: 5),
    servicesWithCharacteristicsToDiscover: {serviceId: [char1, char2]},
    connectionTimeout: const Duration(seconds:  2),
  ).listen((connectionState) {
    // Handle connection state updates
  }, onError: (dynamic error) {
    // Handle a possible error
  });

Besides the normal connection parameters that are described above this function also has 2 additional required parameters: withServices and prescanDuration. PreScanDuration is the amount of time the ble stack will scan for the device before it attempts to connect (if the device is found)

Read / write characteristics

Read characteristic

final characteristic = QualifiedCharacteristic(serviceId: serviceUuid, characteristicId: characteristicUuid, deviceId: foundDeviceId);
final response = await flutterReactiveBle.readCharacteristic(characteristic);

Write with response

Write a value to characteristic and await the response. The "response" in "write characteristic with response" means "an acknowledgement of reception". The write can either be acknowledged (success) or failed (an exception is thrown), thus the return type is void and there is nothing to print (though you can print("Write successful") and in a catch-clause print("Write failed: $e")).

BLE does not provide a request-response mechanism like you may know from HTTP out of the box. If you need to perform request-response calls, you will need to implement a custom mechanism on top of the basic BLE functionality. A typical approach is to implement a "control point": a characteristic that is writable and delivers notifications or indications, so that a request is written to it and a response is delivered back as a notification or an indication.

final characteristic = QualifiedCharacteristic(serviceId: serviceUuid, characteristicId: characteristicUuid, deviceId: foundDeviceId); 
await flutterReactiveBle.writeCharacteristicWithResponse(characteristic, value: [0x00]);

Write without response

Use this operation if you want to execute multiple consecutive write operations in a small timeframe (e.g uploading firmware to device) or if the device does not provide a response. This is performance wise the fastest way of writing a value but there's a chance that the BLE device cannot handle that many consecutive writes in a row, so do a writeWithResponse once in a while.

final characteristic = QualifiedCharacteristic(serviceId: serviceUuid, characteristicId: characteristicUuid, deviceId: foundDeviceId);
flutterReactiveBle.writeCharacteristicWithoutResponse(characteristic, value: [0x00]);

Subscribe to characteristic

Instead of periodically reading the characteristic you can also listen to the notifications (in case the specific service supports it) in case the value changes.

final characteristic = QualifiedCharacteristic(serviceId: serviceUuid, characteristicId: characteristicUuid, deviceId: foundDeviceId);
   flutterReactiveBle.subscribeToCharacteristic(characteristic).listen((data) {
      // code to handle incoming data
    }, onError: (dynamic error) {
      // code to handle errors
    });

Negotiate MTU size

You can increase or decrease the MTU size to reach a higher throughput. This operation will return the actual negotiated MTU size, but it is no guarantee that the requested size will be successfully negotiated. iOS has a default MTU size which cannot be negotiated, however you can still use this operation to get the current MTU.

final mtu = await flutterReactiveBle.requestMtu(deviceId: foundDeviceId, mtu: 250);

Android specific operations

The following operations will only have effect for Android and are not supported by iOS. When using these operations on iOS the library will throw an UnSupportedOperationException.

Request connection priority

On Android you can send a connection priority update to the BLE device. The parameter priority is an enum that uses the same spec as the BluetoothGatt Android spec. Using highPerformance will increase battery usage but will speed up GATT operations. Be cautious when setting the priority when communicating with multiple devices because if you set highperformance for all devices the effect of increasing the priority will be lower.

await flutterReactiveBle.requestConnectionPriority(deviceId: foundDeviceId, priority:  ConnectionPriority.highPerformance);

Clear GATT cache

The Android OS maintains a table per device of the discovered service in cache. Sometimes it happens that after a firmware update a new service is introduced but the cache is not updated. To invalidate the cache you can use the cleargattCache operation.

This is a hidden BLE operation and should be used with extreme caution since this operation is on the greylist.

await flutterReactiveBle.clearGattCache(foundDeviceId);

Contributing

Feel free to open an new issue or a pull request to make this project better

Setup

This project uses melos to manage all the packages inside this repo.

Install melos: dart pub global activate melos Setup melos to point to the dependencies in your local folder: melos bootstrap

Android

Library requires kotlin version 1.5.31.

Update kotlin version

To update the kotlin version open Android studio and go to Tools > Kotlin > Configure Kotlin plugin updates and update Update channel to 1.5.x.

FAQ

How to handle the BLE undeliverable exception

On Android side we use the RxAndroidBle library of Polidea. After migration towards RxJava 2 some of the errors are not routed properly to their listeners and thus this will result in a BLE Undeliverable Exception. The root cause lies in the threading of the Android OS. As workaround RxJava has a hook where you can set the global errorhandler. For more info see RxJava docs .

A default workaround implementation in the Flutter app (needs to be in the Java / Kotlin part e.g. mainactivity) is shown below. For an example (in Java) see Polidea RxAndroidBle sample.

BleException is coming from Polidea RxAndroidBle, so make sure your application declares the following depedency: implementation "com.polidea.rxandroidble2:rxandroidble:1.11.1"

RxJavaPlugins.setErrorHandler { throwable ->
  if (throwable is UndeliverableException && throwable.cause is BleException) {
    return@setErrorHandler // ignore BleExceptions since we do not have subscriber
  }
  else {
    throw throwable
  }
}

Which permissions are needed?

Android

For android the library uses the following permissions, depending on the SDK level:

Up to SDK 30 (Android 11):

  • ACCESS_FINE_LOCATION : this permission is needed because old Nexus devices need location services in order to provide reliable scan results
  • BLUETOOTH : allows apps to connect to a paired Bluetooth device
  • BLUETOOTH_ADMIN: allows apps to discover and pair Bluetooth devices
  • BLUETOOTH_SCAN: add this permission with tools:node="remove" to remove it from the merged manifest.
">

This will prevent issues like #410.

SDK 31 and up (Android 12+):

  • BLUETOOTH_CONNECT: allows apps to connect to a Bluetooth device
  • BLUETOOTH_SCAN: allows apps to scan for Bluetooth devices

These permissions are already added in the manifest of this library and thus should automatically merge into the manifest of your app. It is not needed to add the permissions in your manifest.

Only when using Android SDK 31 (Android 12) and higher please make sure you manifest is correctly setup regarding android:usesPermissionFlags="neverForLocation" for the android:name="android.permission.BLUETOOTH_SCAN" permission, depending on the use cases of your app. See This link for more information and the manifest of the example app for an example usage.

iOS

For iOS it is required you add the following entries to the Info.plist file of your app. It is not allowed to access Core BLuetooth without this. See our example app on how to implement this. For more indepth details: Blog post on iOS bluetooth permissions

iOS13 and higher

  • NSBluetoothAlwaysUsageDescription

iOS12 and lower

  • NSBluetoothPeripheralUsageDescription

How to adjust ProGuard (Android)

In case you are using ProGuard add the following snippet to your proguard-rules.pro file:

-keep class com.signify.hue.** { *; }

This will prevent issues like #131.

Why doesn't the BLE stack directly connect to my peripheral

Before you are able to execute BLE operations the BLE-stack of the device makes sure everything is setup correctly and then reports ready for operation. For some devices this takes a bit longer than for others. When starting the app make sure that the BLE-stack is properly initialized before you execute BLE operations. The safest way to do this is by listening to the statusStream and wait for BleStatus.ready.

This will prevent issues like #147.

Unofficial example apps

Example implementation UART over BLE:link

You might also like...

An alternative random library for Dart.

Randt Randt library for Dart... Description Use Randt to get a random integer from a list, generate random integer in a specific range and generate ra

Nov 21, 2021

A library for YAML manipulation with comment and whitespace preservation.

Yaml Editor A library for YAML manipulation while preserving comments. Usage A simple usage example: import 'package:yaml_edit/yaml_edit.dart'; void

Dec 26, 2022

A comprehensive, cross-platform path manipulation library for Dart.

A comprehensive, cross-platform path manipulation library for Dart. The path package provides common operations for manipulating paths: joining, split

Dec 29, 2022

Boilerplate-free form validation library

Valform Boilerplate-free form validation library. Preface Why? Why not Formz? Why Valform? Getting started Simple Usage Inspiration Why? There is no c

Nov 14, 2021

Boilerplate-free form validation library

Trigger Boilerplate-free form validation library. Preface Why? Why not Formz? Why Trigger? Getting started Simple Usage Inspiration Why? There is no c

Nov 14, 2021

A fast algorithm for finding polygon pole of inaccessibility implemented as a Dart library.

polylabel Dart port of https://github.com/mapbox/polylabel. A fast algorithm for finding polygon pole of inaccessibility implemented as a Dart library

Nov 13, 2021

Dart library for unescaping HTML-encoded strings

html_unescape A Dart library for unescaping HTML-encoded strings. Supports: Named Character References ( ) 2099 of them Decimal Character Referen

Dec 20, 2022

The `TypedEventNotifier` library allows notifying listeners with an object.

The TypedEventNotifier library allows notifying listeners with an object. listeners can be subscribed to only a special type or group of objects.

Nov 13, 2021

A utility library to automate mobile emulators.

emulators A utility library to automate mobile emulators. Can be used to automate screenshots on multiple devices. Example project https://github.com/

Nov 13, 2022
Owner
null
An extension to the Flutter SDK for building Flutter applications for Tizen devices.

Flutter for Tizen An extension to the Flutter SDK for building Flutter applications for Tizen devices. Flutter and the related logo are trademarks of

null 356 Dec 16, 2022
Fluro is a Flutter routing library that adds flexible routing options like wildcards, named parameters and clear route definitions.

Fluro is a Flutter routing library that adds flexible routing options like wildcards, named parameters and clear route definitions.

Luke Pighetti 3.5k Jan 4, 2023
Scribble is a lightweight library for freehand drawing in Flutter supporting pressure, variable line width and more!

Scribble Scribble is a lightweight library for freehand drawing in Flutter supporting pressure, variable line width and more! A

Tim Created It. 73 Dec 16, 2022
The Dart Time Machine is a date and time library for Flutter, Web, and Server with support for timezones, calendars, cultures, formatting and parsing.

The Dart Time Machine is a date and time library for Flutter, Web, and Server with support for timezones, calendars, cultures, formatting and parsing.

null 2 Oct 8, 2021
Redux Compact is a library that aims to reduce the massive amount of boilerplate that follows maintaining a Flutter Redux application.

Redux Compact Redux Compact is a library that aims to reduce the massive amount of boilerplate that follows maintaining a Flutter Redux application. T

Ómar Óskarsson 9 Apr 8, 2022
A Flutter library to make Rest API clients more easily. Inspired by Java Feing.

A Flutter library to make Rest API clients more easily. Inspired by Java Feing. Features Facilitated JSON encode and decode using common interfaces. F

null 2 Mar 15, 2022
Flutter library to add gestures and animations to each Shape you draw on your canvas in your CustomPainter

Flutter library to bring your CustomPainter ?? to Life ✨ ⚡️ touchable library gives you the ability to add various gestures and animations to each Sha

Natesh Bhat 190 Jan 7, 2023
A Flutter plugin for XUpdate -- Android Update Library

flutter_xupdate A Flutter plugin for XUpdate -- Android Update Library。See the use Chinese Document for details。 About me WeChat public number juejin

薛翔 233 Dec 25, 2022
A Dart library to parse Portable Executable (PE) format

pefile A Dart library to parse Portable Executable (PE) format Usage A simple usage example: var pe = pefile.parse('C:\\Windows\\System32\\notepad.exe

null 4 Sep 12, 2022
This library contains methods that make it easy to consume Mpesa Api.

This library contains methods that make it easy to consume Mpesa Api. It's multi-platform, and supports CLI, server, mobile, desktop, and the browser.

Eddie Genius 3 Dec 15, 2021