
Flutter Bluetooth LE development can be a bit overwhelming, but don't worry, I've got you covered.
To start with, Flutter provides a simple and easy-to-use API for Bluetooth Low Energy (BLE) development. This API, known as flutter_blue, allows you to scan for and connect to BLE devices with just a few lines of code.
The flutter_blue API is built on top of the BlueZ library, which is a widely used and reliable Bluetooth stack. This means you can trust that your code will work seamlessly across different platforms.
With flutter_blue, you can easily send and receive data to and from BLE devices. This is particularly useful for applications that require low-power, low-latency communication, such as fitness trackers or smart home devices.
Intriguing read: Bluetooth Low Energy Audio
Getting Started
You can build a Flutter app with Bluetooth LE functionality in just a few steps. First, you'll need to clone the Flutter Blue Sample repository from GitHub using VSCode.
To do this, open the Command Palette in VSCode and enter "Git Clone". Then, enter the URL https://github.com/lupyuen/flutter-blue-sample and select a folder to download the source code.
Once the repository is cloned, you'll need to open the cloned repository and get any missing packages.
To run the app, click Run → Start Debugging in VSCode and select the Dart & Flutter debugger. Wait for the Flutter app to be compiled and deployed to your phone.
Readers also liked: Dual Audio Bluetooth App
Connecting
Connecting to a Bluetooth Low Energy (BLE) device in Flutter is a crucial step in developing a Bluetooth LE application. You can connect to a BLE device by calling the connect method on the BluetoothDevice object.
The connection process can be affected by several factors, such as a low battery on the peripheral device, device misconfiguration, or being on the edge of the Bluetooth range, which can cause radio interference.
To establish a connection, you can use the autoConnect feature, which connects the device whenever it's found. However, if the connection fails, you can troubleshoot the issue by checking the device's battery level, device configuration, or Bluetooth range.
Check this out: Range of Bluetooth Le
Connecting to Display Services
Once you've successfully connected to a BLE device, you can display its services and characteristics.
The Huawei P8 Lite is one of the reported phones to have an issue connecting while scanning, so be sure to stop your scanner before connecting.
Consider reading: Bose Bluetooth Speakers Not Connecting

To display services and characteristics, you'll need to navigate to the new screen after connecting to the device.
On this screen, you'll list the services of the device, and for each service, you'll display its characteristics and include buttons that indicate whether you can read, write, or receive notifications for those specific characteristics.
You can discover the services of the device in the initState, and then list them on the screen.
Each characteristic will have buttons for Read, Write, and Notify, depending on its ability.
To transmit data to the device, you'll need to implement a solution, such as sending the content of a TextField.
The connection process in Flutter_Blue_Plus is more robust with better error handling, and you can listen to connection state changes using the connectionSubscription.
For more insights, see: List of Bluetooth Profiles
Android Pairing Popup Duplicate
Connecting can be a bit finicky, but I've found a solution to a common issue: the Android pairing popup appears twice. You can call createBond() yourself just after connecting and this will resolve the issue.
Sometimes, this duplicate popup can be frustrating, but it's easily fixed.
If you're experiencing this problem, don't worry, there's a simple fix.
Reading and Writing
Reading and writing are essential tasks when working with Bluetooth LE devices. To read data from a characteristic, you first need to listen to characteristic changes and store its new value in a Map. This Map will then update your view and display the updated value of the BLE characteristic.
You can use the events API to access streams from all devices simultaneously. For example, you can use the `events.onCharacteristicReceived` stream to receive notifications when a characteristic is read.
To write data to a characteristic, you'll need to add a Map to store your values by characteristic. This will allow you to display the updated value of the BLE characteristic. You can also use the `write` function to write data to a characteristic.
Here are some key functions to keep in mind:
Remember to check the documentation for specific implementation details and platform-specific requirements.
Read, Write, and Receive Notifications from a Characteristic
So you want to read, write, and receive notifications from a characteristic? Let's break it down.

First, you'll need to add a Map to store your values by characteristic. This will help you display the updated value of the BLE characteristic. You can do this by listening to characteristic changes and storing the new value in your Map.
To read from a characteristic, you can use the read function directly or put it in a StreamSubscription so you can cancel it when you disconnect the BLE device. This will help prevent any unnecessary reads.
Once you've read from the characteristic, you can write to it by adding logic to your read, write, and notify buttons. This will allow you to update the characteristic value.
There are also events you can listen to, like onCharacteristicReceived, which will stream the characteristic value reads of all devices. This can be useful for keeping your app up-to-date with the latest values.
Here's a table of some of the events you can listen to:
By listening to these events, you can keep your app updated with the latest values and ensure a smooth user experience.
On Value Received Is Never Called

If onValueReceived is never called, it's likely a problem with your peripheral device.
lastValueStream will receive data for chr.read() & chr.write() & chr.setNotifyValue(true) and you can try using this to get the latest data from your device.
If you're still experiencing issues, it's possible that your peripheral device is not functioning correctly, so you may want to check its settings or contact the manufacturer for support.
Implementation
To implement Flutter Bluetooth LE, you'll first need to add the `flutter_blue_plus` package to your `pubspec.yaml` file by adding the following line under the `dependencies` section:
dependencies:
sdk: flutter
flutter_blue_plus: ^1.35.5
This package doesn't require creating an instance, you can directly use the static methods provided by the `FlutterBluePlus` class.
Here's a comparison of the original `flutter_blue` package and `flutter_blue_plus`:
You can use static methods directly, such as `FlutterBluePlus.startScan()` and `FlutterBluePlus.scanResults`.
LightBlue App Use Cases
Developers use LightBlue to test and debug their BLE applications, ensuring compatibility with various Bluetooth Low Energy devices. This is especially useful when working with multiple devices, as it saves time and reduces errors.
On a similar theme: Why Is Bluetooth Not Finding Devices Android
The virtual device feature in LightBlue allows developers to simulate BLE peripherals and test IoT applications without needing physical hardware. This is a game-changer for prototyping and testing.
LightBlue helps troubleshoot Bluetooth connectivity issues by analyzing connection parameters and inspecting data packets. I've seen this feature save countless hours of frustration and guesswork.
LightBlue serves as a learning tool for understanding BLE protocols and interactions. It's an excellent resource for those new to BLE development.
Implementation
To implement Bluetooth Low Energy (BLE) functionality in your Flutter app, you'll need to add the flutter_blue_plus package to your pubspec.yaml file. This package provides a simple way to scan for and connect to nearby BLE devices.
To use the flutter_blue_plus package, you'll need to import it in your Dart code by adding the following line at the top of your file:
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
Unlike the original flutter_blue package, flutter_blue_plus doesn't require creating an instance. You can directly use the static methods provided by the FlutterBluePlus class.
Here's a step-by-step guide to implementing BLE functionality in your Flutter app:
1. Add the flutter_blue_plus package to your pubspec.yaml file.
2. Import the package in your Dart code.
3. Use the static methods provided by the FlutterBluePlus class to scan for and connect to nearby BLE devices.
Here are some key methods you can use to implement BLE functionality:
- `FlutterBluePlus.startScan()`: Start scanning for nearby BLE devices.
- `FlutterBluePlus.scanResults`: Get a list of nearby BLE devices.
- `FlutterBluePlus.stopScan()`: Stop scanning for nearby BLE devices.
- `FlutterBluePlus.connect()`: Connect to a BLE device.
By following these steps and using the provided methods, you can easily implement BLE functionality in your Flutter app.
Missing Plugin: No Implementation for XXXX
If you're seeing a MissingPluginException, it's likely because the native plugins aren't loaded.
A common reason for this is that you've just added a plugin to your pubspec.yaml, but a hot reload or hot restart isn't enough to load the native plugins.
You need to fully stop your app and run again to load the native plugins.
Also, try running flutter clean to ensure a clean build.
Error Handling
Flutter Blue Plus takes error handling seriously, checking and throwing exceptions for errors returned by the native platform where appropriate.
Streams returned by FlutterBluePlus never emit errors and never close, so you don't need to handle onError or onDone for stream.listen(...), except for FlutterBluePlus.scanResults.
FlutterBluePlus cannot fix GATT errors, which are responses from your BLE device due to sending an invalid request, and you should handle these errors by checking the error code, such as 'scan_too_frequent'.
Error Handling and Best Practices
Error handling is a crucial aspect of any app development, and Flutter Blue Plus takes it seriously. Every error returned by the native platform is checked and thrown as an exception where appropriate.
FlutterBluePlus never emits any errors and never closes streams, so you don't need to handle onError or onDone for stream.listen(...). However, you should handle onError for FlutterBluePlus.scanResults.
GATT errors are responses from your BLE device due to an invalid request, and FlutterBluePlus cannot fix these errors. You need to check the BLE specification for valid GATT error codes.
For another approach, see: Bluetooth Le Gatt
Handling errors gracefully is essential when working with BLE connections. You should print a message for a connection timeout, as it may indicate the device is out of range.
The BLE connection process is a two-way process, so your BLE device may be misconfigured or refusing the connection. You can try stopping your scanner before connecting, as some phones have issues connecting while scanning.
Characteristic read fails can be caused by a device turning off or crashing during a read, or buggy software on the BLE device. To handle this, you can use a StreamSubscription to cancel the read when you disconnect the BLE device.
When connecting to a BLE device, it's essential to handle connection state changes. You can use the connectionState.listen method to listen to connection state changes and handle errors accordingly.
You should also handle errors when reading characteristics, as they can be caused by a device turning off or crashing during a read. You can print an error message and handle the error accordingly.
Adapter State Called Multiple Times

Forgetting to cancel the original listener is a common mistake that can cause the adapterState to be called multiple times. This can lead to unexpected behavior and errors in your code.
The FlutterBluePlus.adapterState.listen method needs to be cancelled when it's no longer needed to prevent multiple listeners from being created.
If you're experiencing issues with your BLE device, it's possible that it's running low on battery. In this case, you may receive data for chr.read() and chr.setNotifyValue(true) when using the onValueReceived method.
Here are some key takeaways to keep in mind:
- Cancel the original listener when it's no longer needed.
- Check the battery level of your BLE device.
By following these best practices, you can avoid common pitfalls and ensure that your code runs smoothly and efficiently.
Debugging
Debugging can be a frustrating process, but don't worry, I've got some tips to help you out.
First, make sure Bluetooth is turned on on your device. It might seem obvious, but it's easy to overlook.
If you're still having trouble, check the adapterState. If it's not 'on', but you're sure Bluetooth is on, that's a good place to start investigating.
Another common issue is adapterState being called multiple times. This can cause problems with your Bluetooth connection.
Here are some common GATT errors to look out for:
- Characteristic write fails
- Characteristic read fails
If you're using chr.setNotifyValue(true), be aware that your device chooses when to send data. This can cause issues if you're expecting data to be sent at a specific time.
If you're using Flutter, keep in mind that Flutter Errors can occur. For example, if you're using chr.setNotifyValue(true), your device chooses when to send data.
Here are some common problems with scanning:
- Scanning does not find my device
- Scanned device never goes away
- iBeacons not showing
If you're experiencing connection issues, check the following:
- Connection fails
- connectionState is called multiple times
- remoteId is different on Android vs iOS
- iOS: "[Error] The connection has timed out unexpectedly."
If you're not receiving data, check the following:
- onValueReceived is never called (or lastValueStream)
- onValueReceived data is split up (or lastValueStream)
- onValueReceived is called with duplicate data (or lastValueStream)
Here are some Android-specific errors to watch out for:
- ANDROID_SPECIFIC_ERROR
- android pairing popup appears twice
If you're getting a MissingPluginException, it means you need to add access to Bluetooth Hardware in the app's Xcode settings. See Getting Started for more information.
If you're experiencing issues with GATT errors on Android, try interacting with your device to get it to send new data.
Finally, make sure your mtu is large enough to hold your message.
Bluetooth LE
Bluetooth LE is a powerful technology that allows devices to communicate with each other over short distances using low energy consumption. It's a crucial part of many modern applications, including fitness trackers, smart home devices, and even some mobile apps.
To start scanning for Bluetooth LE devices, you can use the FlutterBluePlus package and its startScan function. This function allows you to customize your scan, such as setting a timeout duration or specifying service IDs.
The scanResults stream is a key part of the FlutterBluePlus package, emitting lists of ScanResult objects every time new scan results are available. This allows your app to dynamically update its UI or perform actions based on the discovered devices.
You can also use the FlutterBluePlus.scanResults.listen function to execute a callback function every time new scan results are available. This is useful for updating your app's UI or performing actions based on the discovered devices.
Here's a list of some of the key functions and streams provided by the FlutterBluePlus package:
- startScan: Starts a scan for BLE devices
- stopScan: Stops an existing scan for BLE devices
- scanResults: Stream of live scan results or previous results
- onScanResults: Stream of live scan results
- isScanning: Stream of current scanning state
These functions and streams can be used to build a variety of Bluetooth LE-related features into your app, from device scanning to data transmission.
Power Management
Power management is crucial for Bluetooth Low Energy (BLE) devices, as it allows them to operate for extended periods on battery power.
BLE devices can enter a low-power state called "sleep" to conserve energy, and they can wake up periodically to check for incoming connections or data.
The sleep duration can be adjusted to balance power consumption with responsiveness.
The default sleep duration for BLE devices is 6.4 milliseconds, but it can be changed to a value between 1 and 100 milliseconds.
To minimize power consumption, it's essential to keep the BLE device in sleep mode for as long as possible.
Dependencies and Permissions
To get started with flutter_blue_plus, you'll need to add it as a dependency in your pubspec.yaml file. You can do this by adding 'flutter_blue_plus' to your pubspec.yaml and running the pub get command to install the dependencies.
The flutter_blue_plus plugin is a federated plugin with endorsed platform implementations, which means you only need to add the "app-facing package" to your pubspec.yaml file. It's recommended to pin to a specific version of the flutter_blue_plus plugin for maximum stability and to avoid small breaking changes.
For Android, you'll also need to add permissions, specifically "With Fine Location", and set androidUsesFineLocation when scanning. This is an advanced use case, and you may need to fork the FlutterBluePlus code or submit a pull request (PR) to make it work.
Dependencies
To add the flutter_blue_plus plugin, you'll need to add it to your pubspec.yaml file. This will install the dependencies when you run the pub get command.
The flutter_blue_plus plugin has a federated architecture, which means you only need to add the "app-facing package" to your pubspec.yaml file. This is a key difference from other plugins that require platform-specific implementations.
To ensure maximum stability and avoid small breaking changes, it's recommended to pin to a specific version of the flutter_blue_plus plugin.
The flutter_blue_plus plugin has several platform-specific dependencies, including flutter_blue_plus_android, flutter_blue_plus_darwin, flutter_blue_plus_linux, and flutter_blue_plus_web. These dependencies are used to implement the plugin on different platforms.
These dependencies are listed in the "Dependencies" section of the documentation.
Related reading: Bluetooth File Transfer Software for Android
Add Permissions
Adding permissions to your project can be a bit tricky, but it's a crucial step in ensuring everything runs smoothly.
You can add permissions to a dependency by specifying the required permissions in the dependency's configuration.
Permissions can be added at the module level, so you can control access to specific parts of your project.
A good example of this is the "admin" permission, which allows users to access sensitive areas of the project.
You can also add permissions to specific routes or actions, giving you fine-grained control over what users can do.
For instance, the "create-post" permission allows users to create new posts, while the "edit-post" permission allows them to edit existing ones.
Remember, permissions can be additive, so you can combine multiple permissions to create more complex access controls.
Curious to learn more? Check out: What Is Nearby Devices Permission in Android
Add Android Location Permissions
Adding Android location permissions is a crucial step in building location-based apps.
You'll need to add permissions for Android, specifically "With Fine Location".
This permission is required for apps that need precise location data.
To add this permission, you'll need to set androidUsesFineLocation when scanning.
Note that FlutterBluePlus doesn't support everything, so you may need to fork the library or submit a pull request (PR) to add the feature you need.
Update Android MinSdkVersion
Updating your Android MinSdkVersion is a crucial step when working with certain dependencies.
You should change the minSdkVersion in android/app/build.gradle to 21 if you want to use flutter_blue_plus, which is compatible only from version 21 of Android SDK. This is especially important if you want to use Bluetooth to determine location or support iBeacons.
Recommended read: How Do You Use Bluetooth Speakers
RemoteId Variance Across Platforms
The remoteId is different on Android versus iOS & macOS. This is because iOS & macOS use a randomly generated uuid for privacy reasons, which will periodically change.
One of the main reasons for this difference is that you are not calling the right function. This can lead to issues with authentication and authorization.
A uuid generated on iOS & macOS might look like 6920a902-ba0e-4a13-a35f-6bc91161c517. This is a unique identifier that is generated randomly for each device.
You need to ensure that you are handling remoteId correctly across different platforms to avoid any issues. This includes checking the function being called and the type of uuid being generated.
Explore further: Pixel 9 Bluetooth Issues
Featured Images: pexels.com


