# fluttertpc_flutter_blue_plus **Repository Path**: openharmony-sig/fluttertpc_flutter_blue_plus ## Basic Information - **Project Name**: fluttertpc_flutter_blue_plus - **Description**: No description available - **Primary Language**: Unknown - **License**: BSD-3-Clause - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 6 - **Forks**: 23 - **Created**: 2024-10-10 - **Last Updated**: 2025-05-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ๐Ÿšจ **้‡่ฆๆ็คบ | IMPORTANT** > > **โš ๏ธ ๆญคไปฃ็ ไป“ๅทฒๅฝ’ๆกฃใ€‚ๆ–ฐๅœฐๅ€่ฏท่ฎฟ้—ฎ [fluttertpc_flutter_blue_plus](https://gitcode.com/openharmony-sig/fluttertpc_flutter_blue_plus)ใ€‚| โš ๏ธ This repository has been archived. For the new address, please visit [fluttertpc_flutter_blue_plus](https://gitcode.com/openharmony-sig/fluttertpc_flutter_blue_plus).** > --- > [![pub package](https://img.shields.io/pub/v/flutter_blue_plus.svg)](https://pub.dartlang.org/packages/flutter_blue_plus) [![Chat](https://img.shields.io/discord/634853295160033301.svg?style=flat-square&colorB=758ED3)](https://discord.gg/Yk5Efra)

FlutterBlue



**Note: this plugin is continuous work from [FlutterBlue](https://github.com/pauldemarco/flutter_blue).** Migrating from [FlutterBlue](https://github.com/pauldemarco/flutter_blue)? See [Migration Guide](MIGRATION.md) ## Contents - [Introduction](#introduction) - [Usage](#usage) - [Getting Started](#getting-started) - [Using Ble in App Background](#using-ble-in-app-background) - [Reference](#reference) - [Debugging](#debugging) - [Mocking](#mocking) - [Common Problems](#common-problems) ## Introduction FlutterBluePlus is a Bluetooth Low Energy plugin for [Flutter](https://flutter.dev). It supports BLE Central Role only (most common). If you need BLE Peripheral Role, you should check out [FlutterBlePeripheral](https://pub.dev/packages/flutter_ble_peripheral). ## Tutorial If you are new to Bluetooth, you should start by reading BLE tutorials. * [Novel Bits BLE Tutorial](https://novelbits.io/bluetooth-low-energy-ble-complete-guide/) * [All About Circuits BLE Tutorial](https://www.allaboutcircuits.com/technical-articles/exploring-the-basics-of-bluetooth-low-energy-a-beginners-guide-to-ble/) * [Embetronicx BLE Tutorial](https://embetronicx.com/tutorials/tech_devices/bluetooth-low-energy-ble-introduction-part-1/) ## โ— Bluetooth Classic is not supported โ— i.e. **Arduino HC-05 & HC-06,** speakers, headphones, mice, keyboards, gamepads, 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 supports nearly every feature on all supported platforms: iOS, macOS, Android. FlutterBluePlus was written to be simple, robust, and easy to understand. ## No Dependencies FlutterBluePlus has zero dependencies besides Flutter, Android, iOS, and macOS themselves. This makes FlutterBluePlus very stable, and easy to maintain. ## โญ Stars โญ Please star this repo & on [pub.dev](https://pub.dev/packages/flutter_blue_plus). We all benefit from having a larger community. ## Discord ๐Ÿ’ฌ [![Chat](https://img.shields.io/discord/634853295160033301.svg?style=flat-square&colorB=758ED3)](https://discord.gg/Yk5Efra) There is a community Discord server. ([Link](https://discord.gg/Yk5Efra)) ## 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 seriously. Every error returned by the native platform is checked and thrown as an exception where appropriate. See [Reference](#reference) for a list of throwable functions. **Streams:** Streams returned by FlutterBluePlus 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 ```dart // 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 ### Bluetooth On & Off **Note:** On iOS, a "*This app would like to use Bluetooth*" system dialogue appears on first call to any FlutterBluePlus method. ```dart // first, 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 var subscription = 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(); } // cancel to prevent duplicate listeners subscription.cancel(); ``` ### Scan for devices If your device is not found, see [Common Problems](#common-problems). **Note:** It is recommended to set scan filters to reduce main thread & platform channel usage. ```dart // listen to scan results // Note: `onScanResults` only returns live scan results, i.e. during scanning. Use // `scanResults` if you want live scan results *or* the results from a previous scan. var subscription = FlutterBluePlus.onScanResults.listen((results) { if (results.isNotEmpty) { ScanResult r = results.last; // the most recently found device print('${r.device.remoteId}: "${r.advertisementData.advName}" found!'); } }, onError: (e) => print(e), ); // cleanup: cancel subscription when scanning stops FlutterBluePlus.cancelWhenScanComplete(subscription); // Wait for Bluetooth enabled & permission granted // In your real app you should use `FlutterBluePlus.adapterState.listen` to handle all states await FlutterBluePlus.adapterState.where((val) => val == BluetoothAdapterState.on).first; // Start scanning w/ timeout // Optional: use `stopScan()` as an alternative to timeout await FlutterBluePlus.startScan( withServices:[Guid("180D")], // match any of the specified services withNames:["Bluno"], // *or* any of the specified names timeout: Duration(seconds:15)); // wait for scanning to stop await FlutterBluePlus.isScanning.where((val) => val == false).first; ``` ### Connect to a device ```dart // listen for disconnection var subscription = device.connectionState.listen((BluetoothConnectionState state) async { if (state == BluetoothConnectionState.disconnected) { // 1. typically, start a periodic timer that tries to // reconnect, or just call connect() again right now // 2. you must always re-discover services after disconnection! print("${device.disconnectReasonCode} ${device.disconnectReasonDescription}"); } }); // cleanup: cancel subscription when disconnected // - [delayed] This option is only meant for `connectionState` subscriptions. // When `true`, we cancel after a small delay. This ensures the `connectionState` // listener receives the `disconnected` event. // - [next] if true, the the stream will be canceled only on the *next* disconnection, // not the current disconnection. This is useful if you setup your subscriptions // before you connect. device.cancelWhenDisconnected(subscription, delayed:true, next:true); // Connect to the device await device.connect(); // Disconnect from device await device.disconnect(); // cancel to prevent duplicate listeners subscription.cancel(); ``` ### Auto Connect Connects whenever your device is found. ```dart // enable auto connect // - note: autoConnect is incompatible with mtu argument, so you must call requestMtu yourself await device.connect(autoConnect:true, mtu:null) // wait until connection // - when using autoConnect, connect() always returns immediately, so we must // explicity listen to `device.connectionState` to know when connection occurs await device.connectionState.where((val) => val == BluetoothConnectionState.connected).first; // disable auto connect await device.disconnect() ``` ### MTU On Android, we request an mtu of 512 by default during connection (see: `connect` function arguments). On iOS & macOS, the mtu is negotiated automatically, typically 135 to 255. ```dart final subscription = device.mtu.listen((int mtu) { // iOS: initial value is always 23, but iOS will quickly negotiate a higher value print("mtu $mtu"); }); // cleanup: cancel subscription when disconnected device.cancelWhenDisconnected(subscription); // You can also manually change the mtu yourself. if (Platform.isAndroid) { await device.requestMtu(512); } ``` ### Discover services ```dart // Note: You must call discoverServices after every re-connection! List services = await device.discoverServices(); services.forEach((service) { // do something with service }); ``` ### Read Characteristics ```dart // Reads all characteristics var characteristics = service.characteristics; for(BluetoothCharacteristic c in characteristics) { if (c.properties.read) { List value = await c.read(); print(value); } } ``` ### Write Characteristic ```dart // Writes to a characteristic await c.write([0x12, 0x34]); ``` **allowLongWrite**: To write large characteristics (up to 512 bytes) regardless of mtu, use `allowLongWrite`: ```dart /// 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. ```dart import 'dart:math'; // split write 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 splitWrite(List value, {int timeout = 15}) async { int chunk = device.mtuNow - 3; // 3 bytes ble overhead for (int i = 0; i < value.length; i += chunk) { List subvalue = value.sublist(i, min(i + chunk, value.length)); await write(subvalue, withoutResponse:false, timeout: timeout); } } } ``` ### Subscribe to a characteristic If `onValueReceived` is never called, see [Common Problems](#common-problems) in the README. ```dart final subscription = characteristic.onValueReceived.listen((value) { // onValueReceived is updated: // - anytime read() is called // - anytime a notification arrives (if subscribed) }); // cleanup: cancel subscription when disconnected device.cancelWhenDisconnected(subscription); // subscribe // Note: If a characteristic supports both **notifications** and **indications**, // it will default to **notifications**. This matches how CoreBluetooth works on iOS. 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. ```dart final subscription = 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(subscription); // enable notifications await characteristic.setNotifyValue(true); ``` ### Read and write descriptors ```dart // Reads all descriptors var descriptors = characteristic.descriptors; for(BluetoothDescriptor d in descriptors) { List value = await d.read(); print(value); } // Writes to a descriptor await d.write([0x12, 0x34]) ``` ### Services Changed Characteristic FlutterBluePlus automatically listens to the Services Changed Characteristic (0x2A05) In FlutterBluePlus, we call it `onServicesReset` because you must re-discover services. ```dart // - uses the GAP Services Changed characteristic (0x2A05) // - you must call discoverServices() again device.onServicesReset.listen(() async { print("Services Reset"); await device.discoverServices(); }); ``` ### Save Device To save a device, just write the remoteId somewhere. ```dart // connect without scanning final File file = File('/remoteId.txt'); var device = BluetoothDevice.fromId(await file.readAsString()); await device.connect(); ``` ### Get Connected Devices Get devices currently connected to your app. ```dart List 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. ```dart List 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. ```dart 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.onConnectionStateChanged * events.onMtuChanged * events.onReadRssi * events.onServicesReset * events.onDiscoveredServices * events.onCharacteristicReceived * events.onCharacteristicWritten * events.onDescriptorRead * events.onDescriptorWritten * events.onNameChanged (iOS Only) * events.onBondStateChanged (Android Only) ```dart // listen to *any device* connection state changes FlutterBluePlus.events.onConnectionStateChanged.listen((event)) { print('${event.device} ${event.connectionState}'); } ``` ## Mocking To mock `FlutterBluePlus` for development, refer to the [Mocking Guide](MOCKING.md). ## 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**: ```dart android { defaultConfig { minSdkVersion: 21 ``` ### Add permissions for Android (No Location) In the **android/app/src/main/AndroidManifest.xml** add: ```xml ``` ### Add permissions for Android (With Fine Location) If you want to use Bluetooth to determine location, or support iBeacons. In the **android/app/src/main/AndroidManifest.xml** add: ```xml ``` And set **androidUsesFineLocation** when scanning: ```dart // 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: ```dart NSBluetoothAlwaysUsageDescription This app needs Bluetooth to function ``` For location permissions on iOS see more at: [https://developer.apple.com/documentation/corelocation/requesting_authorization_for_location_services](https://developer.apple.com/documentation/corelocation/requesting_authorization_for_location_services) ### Add permissions for macOS Make sure you have granted access to the Bluetooth hardware: `Xcode -> Runners -> Targets -> Runner-> Signing & Capabilities -> App Sandbox -> Hardware -> Enable Bluetooth` Screenshot 2023-12-11 at 10 32 04 AM ## Using Ble in App Background **This is an advanced use case**. FlutterBluePlus does not support everything. You may have to fork it. PRs are welcome. ### iOS Documentation: https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html Add the following to your `Info.plist` ``` UIBackgroundModes bluetooth-central ``` When this key-value pair is included in the appโ€™s Info.plist file, the system wakes up your app to process ble `read`, `write`, and `subscription` events. You may also have to use https://pub.dev/packages/flutter_isolate **Note**: Upon being woken up, an app has around 10 seconds to complete a task. Apps that spend too much time executing in the background can be throttled back by the system or killed. ### Android You can try using https://pub.dev/packages/flutter_foreground_task or possibly https://pub.dev/packages/flutter_isolate ## Reference ๐ŸŒ€ = Stream โšก = synchronous ### FlutterBluePlus API | | Android | iOS | Throws | Description | | :--------------------- | :----------------: | :----------------: | :----: | :----------------------------------------------------------| | setLogLevel | :white_check_mark: | :white_check_mark: | | Configure plugin log level | | setOptions | :white_check_mark: | :white_check_mark: | | Set configurable bluetooth options | | isSupported | :white_check_mark: | :white_check_mark: | | Checks whether the device supports Bluetooth | | turnOn | :white_check_mark: | | :fire: | Turns on the bluetooth adapter | | adapterStateNow โšก | :white_check_mark: | :white_check_mark: | | Current state of the bluetooth adapter | | adapterState ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of on & off states of the bluetooth adapter | | startScan | :white_check_mark: | :white_check_mark: | :fire: | Starts a scan for Ble devices | | stopScan | :white_check_mark: | :white_check_mark: | :fire: | Stop an existing scan for Ble devices | | onScanResults ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of live scan results | | scanResults ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of live scan results or previous results | | lastScanResults โšก | :white_check_mark: | :white_check_mark: | | The most recent scan results | | isScanning ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of current scanning state | | isScanningNow โšก | :white_check_mark: | :white_check_mark: | | Is a scan currently running? | | connectedDevices โšก | :white_check_mark: | :white_check_mark: | | List of devices connected to *your app* | | systemDevices | :white_check_mark: | :white_check_mark: | :fire: | List of devices connected to the system, even by other apps| | getPhySupport | :white_check_mark: | | :fire: | Get supported bluetooth phy codings | ### FlutterBluePlus Events API | | Android | iOS | Throws | Description | | :--------------------------------- | :----------------: | :----------------: | :----: | :-----------------------------------------------------| | events.onConnectionStateChanged ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of connection changes of *all devices* | | events.onMtuChanged ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of mtu changes of *all devices* | | events.onReadRssi ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of rssi reads of *all devices* | | events.onServicesReset ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of services resets of *all devices* | | events.onDiscoveredServices ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of services discovered of *all devices* | | events.onCharacteristicReceived ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of characteristic value reads of *all devices* | | events.onCharacteristicWritten ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of characteristic value writes of *all devices*| | events.onDescriptorRead ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of descriptor value reads of *all devices* | | events.onDescriptorWritten ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of descriptor value writes of *all devices* | | events.onBondStateChanged ๐ŸŒ€ | :white_check_mark: | | | Stream of android bond state changes of *all devices* | | events.onNameChanged ๐ŸŒ€ | | :white_check_mark: | | Stream of iOS name changes of *all devices* | ### BluetoothDevice API | | Android | iOS | Throws | Description | | :------------------------ | :----------------: | :----------------: | :----: | :----------------------------------------------------------| | platformName โšก | :white_check_mark: | :white_check_mark: | | The platform preferred name of the device | | advName โšก | :white_check_mark: | :white_check_mark: | | The advertised name of the device found during scanning | | connect | :white_check_mark: | :white_check_mark: | :fire: | Establishes a connection to the device | | disconnect | :white_check_mark: | :white_check_mark: | :fire: | Cancels an active or pending connection to the device | | isConnected โšก | :white_check_mark: | :white_check_mark: | | Is this device currently connected to *your app*? | | isDisonnected โšก | :white_check_mark: | :white_check_mark: | | Is this device currently disconnected from *your app*? | | connectionState ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of connection changes for the Bluetooth Device | | discoverServices | :white_check_mark: | :white_check_mark: | :fire: | Discover services | | servicesList โšก | :white_check_mark: | :white_check_mark: | | The current list of available services | | onServicesReset ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | The services changed & must be rediscovered | | mtu ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of current mtu value + changes | | mtuNow โšก | :white_check_mark: | :white_check_mark: | | The current mtu value | | readRssi | :white_check_mark: | :white_check_mark: | :fire: | Read RSSI from a connected device | | requestMtu | :white_check_mark: | | :fire: | Request to change the MTU for the device | | requestConnectionPriority | :white_check_mark: | | :fire: | Request to update a high priority, low latency connection | | bondState ๐ŸŒ€ | :white_check_mark: | | | Stream of device bond state. Can be useful on Android | | createBond | :white_check_mark: | | :fire: | Force a system pairing dialogue to show, if needed | | removeBond | :white_check_mark: | | :fire: | Remove Bluetooth Bond of device | | setPreferredPhy | :white_check_mark: | | :fire: | Set preferred RX and TX phy for connection and phy options | | clearGattCache | :white_check_mark: | | :fire: | Clear android cache of service discovery results | ### BluetoothCharacteristic API | | Android | iOS | Throws | Description | | :----------------- | :----------------: | :----------------: | :----: | :--------------------------------------------------------------| | uuid โšก | :white_check_mark: | :white_check_mark: | | The uuid of characteristic | | read | :white_check_mark: | :white_check_mark: | :fire: | Retrieves the value of the characteristic | | write | :white_check_mark: | :white_check_mark: | :fire: | Writes the value of the characteristic | | setNotifyValue | :white_check_mark: | :white_check_mark: | :fire: | Sets notifications or indications on the characteristic | | isNotifying โšก | :white_check_mark: | :white_check_mark: | | Are notifications or indications currently enabled | | onValueReceived ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of characteristic value updates received from the device| | lastValue โšก | :white_check_mark: | :white_check_mark: | | The most recent value of the characteristic | | lastValueStream ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of onValueReceived + writes | ### BluetoothDescriptor API | | Android | iOS | Throws | Description | | :---- | :----------------: | :----------------: | :----: | :----------------------------------------------| | uuid โšก | :white_check_mark: | :white_check_mark: | | The uuid of descriptor | | read | :white_check_mark: | :white_check_mark: | :fire: | Retrieves the value of the descriptor | | write | :white_check_mark: | :white_check_mark: | :fire: | Writes the value of the descriptor | | onValueReceived ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | Stream of descriptor value reads & writes | | lastValue โšก | :white_check_mark: | :white_check_mark: | | The most recent value of the descriptor | | lastValueStream ๐ŸŒ€ | :white_check_mark: | :white_check_mark: | | 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. Adapter: - [bluetooth must be turned on](#bluetooth-must-be-turned-on) - [adapterState is not 'on' but my Bluetooth is on](#adapterstate-is-not-on-but-my-bluetooth-is-on) - [adapterState is called multiple times](#adapterstate-is-called-multiple-times) Scanning: - [Scanning does not find my device](#scanning-does-not-find-my-device) - [Scanned device never goes away](#scanned-device-never-goes-away) - [iBeacons not showing](#ibeacons-not-showing) Connecting: - [Connection fails](#connection-fails) - [connectionState is called multiple times](#connectionstate-is-called-multiple-times) - [remoteId is different on Android vs iOS](#the-remoteid-is-different-on-android-versus-ios--macos) - [iOS: "[Error] The connection has timed out unexpectedly."](#ios-error-the-connection-has-timed-out-unexpectedly) Reading & Writing: - [List of Bluetooth GATT Errors](#list-of-bluetooth-gatt-errors) - [Characteristic write fails](#characteristic-write-fails) - [Characteristic read fails](#characteristic-read-fails) Subscriptions: - [onValueReceived is never called (or lastValueStream)](#onvaluereceived-is-never-called-or-lastvaluestream) - [onValueReceived data is split up (or lastValueStream)](#onvaluereceived-data-is-split-up-or-lastvaluestream) - [onValueReceived is called with duplicate data (or lastValueStream)](#onvaluereceived-is-called-with-duplicate-data-or-lastvaluestream) Android Errors: - [ANDROID_SPECIFIC_ERROR](#android_specific_error) - [android pairing popup appears twice](#android-pairing-popup-appears-twice) Flutter Errors: - [MissingPluginException(No implementation found for method XXXX ...)](#missingpluginexceptionno-implementation-found-for-method-xxxx-) --- ### "bluetooth must be turned on" You need to wait for the bluetooth adapter to fully turn on. `await FlutterBluePlus.adapterState.where((state) => state == BluetoothAdapterState.on).first;` You can also use `FlutterBluePlus.adapterState.listen(...)`. See [Usage](#usage). --- ### adapterState is not 'on' but my Bluetooth is on **For iOS:** `adapterState` always starts as `unknown`. You need to wait longer for the service to initialize. Use this code: ``` // wait for actual adapter state, up to 3 seconds Set inProgress = {BluetoothAdapterState.unknown, BluetoothAdapterState.turningOn}; var adapterState = FlutterBluePlus.adapterState.where((v) => !inProgress.contains(v)).first; await adapterState.timeout(const Duration(seconds: 3)).onError((error, stackTrace) { throw Exception("Could not determine Bluetooth state. ${FlutterBluePlus.adapterStateNow}"); }); // check adapter state if (FlutterBluePlus.adapterStateNow != BluetoothAdapterState.on) { throw Exception("Bluetooth Is Not On. ${FlutterBluePlus.adapterStateNow}"); } ``` If `adapterState` is `unavailable`, you must add access to Bluetooth Hardware in the app's Xcode settings. See [Getting Started](#getting-started). **For Android:** Check that your device supports Bluetooth & has permissions. --- ### adapterState is called multiple times You are forgetting to cancel the original `FlutterBluePlus.adapterState.listen` resulting in multiple listeners. ```dart // tip: using ??= makes it easy to only make new listener when currently null final subscription ??= FlutterBluePlus.adapterState.listen((value) { // ... }); // also, make sure you cancel the subscription when done! subscription.cancel() ``` --- ### Scanning does not find my device **1. you're using an emulator** Use a physical device. **2. try using another ble scanner app** * **iOS**: [nRF Connect](https://apps.apple.com/us/app/nrf-connect-for-mobile/id1054362403) * **Android**: [BLE Scanner](https://play.google.com/store/apps/details?id=com.macdom.ble.blescanner) Install a BLE scanner app on your phone. Can it find your device? **3. 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. **4. your device stopped advertising.** - you might need to reboot your device - you might need to 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: ```dart // search system devices. i.e. any device connected to by *any* app List 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 } } ``` **5. your scan filters are wrong.** - try removing all scan filters - for `withServices` to work, your device must actively advertise the serviceUUIDs it supports **6. Android: you're calling startScan too often** On Adroid you can only call `startScan` 5 times per 30 second period. This is a platform restriction. --- ### Scanned device never goes away This is expected. You must set the `removeIfGone` scan option if you want the device to go away when no longer available. --- ### iBeacons Not Showing **iOS:** iOS does not support iBeacons using CoreBluetooth. You must find a plugin meant for CoreLocation. **Android:** 1. you need to enable location permissions, see [Getting Started](#getting-started) 2. you must pass `androidUsesFineLocation:true` to the `startScan` method. --- ### Connection fails **1. Your ble device may be low battery** Bluetooth can become erratic when your peripheral device is low on 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. --- ### connectionState is called multiple times You are forgetting to cancel the original `device.connectionState.listen` resulting in multiple listeners. ```dart // tip: using ??= makes it easy to only make new listener when currently null final subscription ??= FlutterBluePlus.device.connectionState.listen((value) { // ... }); // also, make sure you cancel the subscription when done! subscription.cancel() ``` --- ### The remoteId is different on Android versus iOS & macOS This is expected. There is no way to avoid it. For privacy, iOS & macOS use a randomly generated uuid. This uuid will periodically change. e.g. `6920a902-ba0e-4a13-a35f-6bc91161c517` Android uses the mac address of the bluetooth device. It never changes. e.g. `05:A4:22:31:F7:ED` --- ### iOS: "[Error] The connection has timed out unexpectedly." You can google this error. It is a common iOS ble error code. It means your device stopped working. FlutterBluePlus cannot fix it. --- ### List of Bluetooth GATT Errors These GATT error codes are part of the BLE Specification. **These are *responses* from your ble device because you are sending an invalid request.** FlutterBluePlus cannot fix these errors. You are doing something wrong & your device is responding with an error. **GATT errors as they appear on iOS**: ``` apple-code: 1 | The handle is invalid. apple-code: 2 | Reading is not permitted. apple-code: 3 | Writing is not permitted. apple-code: 4 | The command is invalid. apple-code: 6 | The request is not supported. apple-code: 7 | The offset is invalid. apple-code: 8 | Authorization is insufficient. apple-code: 9 | The prepare queue is full. apple-code: 10 | The attribute could not be found. apple-code: 11 | The attribute is not long. apple-code: 12 | The encryption key size is insufficient. apple-code: 13 | The value's length is invalid. apple-code: 14 | Unlikely error. apple-code: 15 | Encryption is insufficient. apple-code: 16 | The group type is unsupported. apple-code: 17 | Resources are insufficient. apple-code: 18 | Unknown ATT error. ``` **GATT errors as they appear on Android**: ``` android-code: 1 | GATT_INVALID_HANDLE android-code: 2 | GATT_READ_NOT_PERMITTED android-code: 3 | GATT_WRITE_NOT_PERMITTED android-code: 4 | GATT_INVALID_PDU android-code: 5 | GATT_INSUFFICIENT_AUTHENTICATION android-code: 6 | GATT_REQUEST_NOT_SUPPORTED android-code: 7 | GATT_INVALID_OFFSET android-code: 8 | GATT_INSUFFICIENT_AUTHORIZATION android-code: 9 | GATT_PREPARE_QUEUE_FULL android-code: 10 | GATT_ATTR_NOT_FOUND android-code: 11 | GATT_ATTR_NOT_LONG android-code: 12 | GATT_INSUFFICIENT_KEY_SIZE android-code: 13 | GATT_INVALID_ATTRIBUTE_LENGTH android-code: 14 | GATT_UNLIKELY android-code: 15 | GATT_INSUFFICIENT_ENCRYPTION android-code: 16 | GATT_UNSUPPORTED_GROUP android-code: 17 | GATT_INSUFFICIENT_RESOURCES ``` **Descriptions**: ``` 1 | Invalid Handle | The attribute handle given was not valid on this server. 2 | Read Not Permitted | The attribute cannot be read. 3 | Write Not Permitted | The attribute cannot be written. 4 | Invalid PDU | The attribute PDU was invalid. 5 | Insufficient Authentication | The attribute requires authentication before it can be read or written. 6 | Request Not Supported | Attribute server does not support the request received from the client. 7 | Invalid Offset | Offset specified was past the end of the attribute. 8 | Insufficient Authorization | The attribute requires an authorization before it can be read or written. 9 | Prepare Queue Full | Too many prepare writes have been queued. 10 | Attribute Not Found | No attribute found within the given attribute handle range. 11 | Attribute Not Long | The attribute cannot be read or written using the Read Blob or Write Blob requests. 12 | Insufficient Key Size | The Encryption Key Size used for encrypting this link is insufficient. 13 | Invalid Attribute Value Length | The attribute value length is invalid for the operation. 14 | Unlikely Error | The request has encountered an unlikely error and cannot be completed. 15 | Insufficient Encryption | The attribute requires encryption before it can be read or written. 16 | Unsupported Group Type | The attribute type is not a supported grouping as defined by a higher layer. 17 | Insufficient Resources | Insufficient Resources to complete the request. ``` --- ### characteristic write fails First, check the [List of Bluetooth GATT Errors](#list-of-bluetooth-gatt-errors) for your error. **1. your bluetooth device turned off, or is out of range** If your device turns off or crashes during a write, it will cause a failure. **2. Your Bluetooth device has bugs** Maybe your device crashed, or is not sending a response due to software bugs. **3. there is radio interference** Bluetooth is wireless and will not always work. --- ### Characteristic read fails First, check the [List of Bluetooth GATT Errors](#list-of-bluetooth-gatt-errors) for your error. **1. your bluetooth device turned off, or is out of range** If your device turns off or crashes during a read, it will cause a failure. **2. Your Bluetooth device has bugs** Maybe your device crashed, or is not sending a response due to software bugs. **3. there is radio interference** Bluetooth is wireless and will not always work. --- ### onValueReceived is never called (or lastValueStream) **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 data is split up (or lastValueStream) Verify that the mtu is large enough to hold your message. ```dart device.mtu ``` If it still happens, it is a problem with your peripheral device. --- ### onValueReceived is called with duplicate data (or lastValueStream) You are probably forgetting to cancel the original `chr.onValueReceived.listen` resulting in multiple listens. The easiest solution is to use `device.cancelWhenDisconnected(subscription)` to cancel device subscriptions. ```dart final subscription = chr.onValueReceived.listen((value) { // ... }); // make sure you have this line! device.cancelWhenDisconnected(subscription); await characteristic.setNotifyValue(true); ``` --- ### ANDROID_SPECIFIC_ERROR There is no 100% solution. FBP already has mitigations for this error, but Android will still fail with this code randomly. The recommended solution is to `catch` the error, and retry. --- ### android pairing popup appears twice This is a bug in android itself. You can call `createBond()` yourself just after connecting and this will resolve the issue. --- ### MissingPluginException(No implementation found for method XXXX ...) If you just added flutter_blue_plus to your pubspec.yaml, a hot reload / hot restart is not enough. You need to fully stop your app and run again so that the native plugins are loaded. Also try `flutter clean`.