The Flutter Splendid BLE plugin offers a robust suite of functionalities for Bluetooth Low Energy ( BLE) interactions in Flutter applications. It allows Flutter apps to use Bluetooth for interacting with peripheral devices. This includes scanning for and connecting to BLE peripherals, managing bonding processes, writing and reading values, subscribing to BLE characteristics, and more. This plugin provides a comprehensive tool for versatile BLE operations.
This plugin allows a Flutter app to act as a BLE central device. It is designed for scenarios where the app scans for, connects to, and interacts with BLE peripheral devices. Key functionalities include:
Example Use-Cases:
Efficient Toolset: The primary objective is to provide developers with an efficient set of tools for BLE interactions, reducing the need to rely on multiple libraries or native code.
Best Practices: The plugin is developed following all Flutter and Dart best practices, ensuring smooth integration into any Flutter project and consistency with the broader Flutter ecosystem.
Best-in-class Documentation: Good software should be accompanied by excellent documentation. As such, every class, variable, and method in this plugin is accompanied by detailed and easy-to-understand documentation to aid developers at all levels in leveraging the full potential of this plugin.
First, add the following line to your pubspec.yaml
:
dependencies:
flutter_splendid_ble: ^0.12.0
Then run:
flutter pub get
In the files in which you wish to use the plugin, import it by adding:
import 'package:flutter_splendid_ble/splendid_ble.dart';
Before using the flutter_splendid_ble plugin in your Flutter project, you need to ensure that the necessary configurations are in place for the iOS/macOS and Android platforms, depending upon which platforms your Flutter app will be targeting. This section details the required prerequisites for each platform.
To use Bluetooth functionality in your Flutter app on iOS and macOS, you need to add specific key/value pairs to your Info.plist files. These keys inform the system about your app’s usage of Bluetooth and the reasons for accessing it. Below are the required keys and their descriptions:
<key>NSBluetoothAlwaysUsageDescription</key><string>This app uses Bluetooth to connect to external
devices.
</string>
<key>NSBluetoothPeripheralUsageDescription</key><string>This app uses Bluetooth to communicate with
external peripherals.
</string>
<key>NSBluetoothAlwaysAndWhenInUseUsageDescription</key><string>This app uses Bluetooth to connect
to external devices at all times.
</string>
Ensure that your project has the necessary capabilities enabled to use Bluetooth features:
To use Bluetooth functionality in your Flutter app on Android, you need to declare specific permissions and features in your AndroidManifest.xml file. Additionally, you may need to request runtime permissions if targeting Android 6.0 (API level 23) or higher.
To use Bluetooth functionality in your Flutter app on Android, you need to declare specific permissions and features in your AndroidManifest.xml file. Additionally, you may need to request runtime permissions if targeting Android 6.0 (API level 23) or higher.
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-feature android:name="android.hardware.bluetooth" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
All Bluetooth functionality provided by this plugin goes through either the SplendidBleCentral
class (which is also aliased as SplendidBle
for backwards compatibility). So, wherever you need
to conduct Bluetooth operations, you will need an instance of this class.
import 'package:flutter_splendid_ble/splendid_ble_plugin.dart';
final SplendidBleCentral bleCentral = SplendidBleCentral();
You could simply instantiate the SplendidBleCentral
class in each Dart class where Bluetooth
functionality is needed or, depending upon your needs and the architecture of your application, you
could create a centralized service where these instances are created once and referenced from
everywhere else in your codebase. Some of the examples below show a service class being used to wrap
functionality provided by this plugin.
If targeting Android 6.0 (API level 23) or higher, you need to request the necessary Bluetooth permissions at runtime. If targeting iOS, Bluetooth permissions must always be requested. This is required before performing any Bluetooth related actions, including checking the state of the Bluetooth adapter (described below). Here is an example of how to request permissions in your Flutter app:
Example
/// A [SplendidBleCentral] instance used to access BLE functionality.
final SplendidBleCentral _ble = SplendidBleCentral();
/// A [StreamSubscription] used to listen for changes in the state of the Bluetooth permissions
/// on the host platform.
StreamSubscription<BluetoothPermissionStatus>? _bluetoothPermissionStream;
/// The current status of the Bluetooth permissions on the host platform, represented by the
/// [BluetoothPermissionStatus] enum.
BluetoothPermissionStatus? _bluetoothPermissionStatus;
/// Initializes Bluetooth permission status monitoring.
///
/// This method sets up a listener to monitor the current status of the Bluetooth permissions on the host platform.
void _initBluetoothPermissionStatusMonitor() {
_bluetoothPermissionStream = _ble.emitCurrentPermissionStatus().listen(
(status) {
_bluetoothPermissionStatus = status;
// Perform actions based on the Bluetooth permission status.
},
onError: (error) {
// Handle any errors encountered while listening to permission status updates.
},
);
// Request Bluetooth permissions. If they have already been granted, this method will do nothing.
_ble.requestBluetoothPermissions();
}
void dispose() {
// Cancel the Bluetooth permission stream.
_bluetoothPermissionStream?.cancel();
super.dispose();
}
Notes and Best Practices
Before you can use any Bluetooth functionality, you should ensure that the device's Bluetooth adapter is ready. This step is crucial because attempting to use Bluetooth features when the adapter is not available or turned off will lead to failures and a poor user experience.
In the example below, the method, _checkAdapterStatus
, is responsible for setting up a listener
for the state of the host device's Bluetooth adapter. It uses a stream (_bluetoothStatusStream
)
which emits the current status of the Bluetooth adapter.
The possible states of the Bluetooth adapter are defined in the BluetoothStatus
enum, which
includes three possible states:
enabled
: The adapter is on and available for use.disabled
: The adapter is off and needs to be enabled before proceeding.notAvailable
: The device does not have a Bluetooth adapter.Here's how you might perform this check:
notAvailable
or showing an error message to the user.Including a robust status check at the beginning of your Bluetooth workflow ensures that all subsequent operations have a higher chance of success and that your app behaves predictably in the face of changing conditions.
Example
import 'dart:async';
/// [SplendidBleCentral] instance providing BLE functionality.
final SplendidBleCentral _ble = SplendidBleCentral();
/// A [StreamSubscription] used to listen for changes in the state of the Bluetooth adapter.
StreamSubscription<BluetoothStatus>? _bluetoothStatusStream;
/// Initializes Bluetooth status monitoring.
void initBluetoothStatusMonitor() {
_checkAdapterStatus();
}
/// Initializes Bluetooth status monitoring.
///
/// This method sets up a listener to monitor the current status of the Bluetooth adapter. It is typically called
/// during the initialization phase of the app or when Bluetooth monitoring is required.
void _checkAdapterStatus() async {
_bluetoothStatusStream = _ble.emitCurrentBluetoothStatus().listen(
(status) {
// Update your UI or logic based on the Bluetooth status.
},
onError: (error) {
// Handle any errors encountered while listening to status updates.
}
);
}
/// Dispose stream subscription when it's no longer needed to prevent memory leaks.
void dispose() {
_bluetoothStatusStream?.cancel();
}
Notes and Best Practices
In some cases, a Bluetooth device with which you want to interact may already be connected to the host device. In such scenarios, you may want to retrieve a list of connected devices to check if the desired device is already connected or to display a list of connected devices to the user. You might also append (or prepend) the connected devices to the list of discovered devices during a Bluetooth scan to ensure that the user can see all available devices, including those that are already connected.
The example below demonstrates how to retrieve a list of connected Bluetooth devices. For each
device, the name and address are returned. This information is represented by objects of the
ConnectedBluetoothDevice
class.
Example
/// Get a list of Bluetooth devices that are currently connected to the host device.
// TODO: replace the service UUID with a value from your own system
Future<void> _getConnectedDevices() async {
try {
final List<ConnectedBleDevice> devices = await _ble.getConnectedDevices(
['abcd1234-1234-1234-1234-1234567890aa']);
debugPrint('Connected devices: $connectedDevices');
// TODO: use the list of devices as needed
} on BluetoothScanException catch (e) {
_showErrorMessage(e.message);
}
}
Notes and Best Practices
getConnectedDevices
method.
These platforms do not support getting a list of all connected devices. Rather, a service UUID
must be provided to filter the list of connected devices.Scanning for nearby Bluetooth Low Energy (BLE) devices is a fundamental feature for BLE-enabled applications. It's important to conduct these scans responsibly to balance the need for device discovery against the impact on battery life and user privacy.
Below is a guide and example code snippet for starting a Bluetooth device scan:
Prerequisites Ensure that you have already checked the Bluetooth adapter status as shown above to ensure that Bluetooth is turned on and available for scanning. You should also handle the necessary permissions for Bluetooth usage as required by the platform (e.g., location permissions for Android).
Starting the Scan
In the example below, the _startBluetoothScan
method is responsible for initiating a scan for BLE
devices. It uses the Splendid BLE plugin's scanning method, which typically takes filters and
settings as parameters to customize the scan behavior.
Handling Discovered Devices When a BLE device is discovered, the scan stream emits device information. The '_onDeviceDetected' method is then called with this device information, allowing you to handle new devices as needed (e.g., updating the UI, starting a connection, or reading services and characteristics).
Example
import 'dart:async';
/// [SplendidBleCentral] instance as discussed above.
final SplendidBleCentral _ble = SplendidBleCentral();
/// A [StreamSubscription] used to listen for newly discovered BLE devices.
StreamSubscription<BluetoothDevice>? _scanStream;
/// Begins a scan for nearby BLE devices.
void _startBluetoothScan() {
// Assuming `filters` and `settings` are already defined and passed to the widget.
_scanStream = _ble
.startScan(filters: filters, settings: settings)
.listen((device) => _onDeviceDetected(device), onError: _handleScanError);
}
/// Called when a new device is detected by the Bluetooth scan.
void _onDeviceDetected(BluetoothDevice device) {
// Handle the discovered device, e.g., by updating a list or attempting a connection.
}
/// Handles any errors that occur during the scan.
void _handleScanError(Object error) {
// Handle scan error, possibly by stopping the scan, reporting the error, and updating the UI.
}
/// Stops the Bluetooth scan.
Future<void> stopScan() async {
try {
await SplendidBleMethodChannel.stopScan();
// Handle successful scan stop if necessary.
} on BluetoothScanException catch (e) {
// Handle the exception, possibly by showing an error message to the user.
}
}
/// Disposes of the stream subscription when it's no longer needed to prevent memory leaks.
void dispose() {
_scanStream?.cancel();
}
Notes and Best Practices
Establishing a connection with a Bluetooth Low Energy (BLE) device is a key step to enable communication for data transfer and device interaction. The process of connecting to a BLE device typically involves using the device's address obtained from the discovery scan.
Below is a guide and example code snippet for connecting to a BLE device:
Prerequisites Ensure you have successfully discovered BLE devices and have access to the device address. Additionally, ensure the user has granted any permissions necessary for connecting to a Bluetooth device.
Initiating a Connection
To connect to a BLE device, you will use the connect
method provided by the Splendid BLE plugin.
This method requires the address of the device, which is usually obtained from the discovery
process.
Connection State Updates Once the connection attempt has been initiated, the connect method will return a stream that emits the connection state updates. You should listen to this stream to receive updates and handle them accordingly.
Error Handling It is important to handle any exceptions that may occur during the connection attempt. This could be due to the device being out of range, the device not being connectable, or other reasons.
Example
import 'dart:async';
/// [SplendidBleCentral] instance as discussed above.
final SplendidBleCentral _ble = SplendidBleCentral();
/// A [StreamSubscription] used to listen for changes in the connection status between the app and the [BleDevice].
StreamSubscription<BleConnectionState>? _connectionStream;
/// Attempt to connect to the [BleDevice].
void _connectToDevice(BleDevice device) {
try {
_connectionStream = _ble.connect(deviceAddress: device.address).listen(
_onConnectionStateUpdate,
);
} catch (e) {
debugPrint('Failed to connect to device, ${device.address}, with exception, $e');
_handleConnectionError(e);
}
}
/// Handles errors resulting from an attempt to connect to a peripheral.
void _handleConnectionError(Object error) {
// Handle errors in connecting to a peripheral.
}
/// Called when the connection state is updated.
void _onConnectionStateUpdate(ConnectionState state) {
// Handle the updated connection state, e.g., by updating the UI or starting service discovery.
}
void dispose() {
// Cancel the connection state stream.
_connectionStream?.cancel();
}
Notes and Best Practices
After establishing a connection with a Bluetooth Low Energy (BLE) device, the next step is to discover the services and characteristics offered by the peripheral. This process is crucial for determining how to interact with the device, as services and characteristics define the functionalities available.
Understanding Service Discovery
Initiating Service Discovery
Service discovery is initiated once a connection with a BLE device has been successfully
established. Service discovery is performed by calling the discoverServices
method from the
Splendid BLE plugin.
Example
import 'dart:async';
/// SplendidBleCentral instance as discussed above.
final SplendidBleCentral _ble = SplendidBleCentral();
/// A [StreamSubscription] used to listen for discovered services.
StreamSubscription<List<BleService>>? _servicesDiscoveredStream;
/// Starts the service discovery process for the connected BLE device.
// Replace `widget.device.address` with the Bluetooth address of your device
void startServiceDiscovery() {
_servicesDiscoveredStream = _ble.discoverServices(widget.device.address).listen(
_onServiceDiscovered,
);
}
/// Called when services are discovered.
void _onServiceDiscovered(List<BleService> services) {
// Process the discovered service.
}
/// Dispose method to cancel the subscription when it's no longer needed.
void dispose() {
_servicesDiscoveredStream?.cancel();
}
Notes and Best Practices
Bluetooth Low Energy (BLE) characteristics can be configured to notify or indicate a connected device when their value changes. This is a powerful feature that allows a BLE peripheral to send updates asynchronously to a central device without the central having to poll for changes. In other words, subscribing to notifications or indications from a BLE characteristic is essential for real-time communication in BLE applications.
When subscribing to BLE characteristic notifications or indications using the Splendid BLE plugin,
the
values you receive are instances of BleCharacteristicValue, which contain the raw data as List
Notifications vs. Indications
Prerequisites
Ensure that the BLECharacteristic
supports either notifications or indications. Check the
characteristic's properties before attempting to subscribe.
Setting Up Characteristic Subscription To listen for changes in a characteristic's value, you subscribe to the characteristic. When subscribed, the BLE peripheral will start sending updates whenever the characteristic’s value changes.
Example
import 'dart:async';
import 'package:flutter_splendid_ble/splendid_ble.dart';
SplendidBleCentral _blePlugin;
/// A [BLECharacteristic] instance for a characteristic that supports indications or
/// notifications.
BLECharacteristic _characteristic;
/// A [StreamSubscription] used to listen for updates in the value of a characteristic.
StreamSubscription<BleCharacteristicValue>? _characteristicValueListener;
/// Constructor accepts a SplendidBle instance and a BLECharacteristic instance.
BLECharacteristicListener(this._blePlugin, this._characteristic);
/// Subscribes to the characteristic updates.
void subscribeToCharacteristic() {
if (_characteristic.properties.notify || _characteristic.properties.indicate) {
_characteristicValueListener = _blePlugin.subscribeToCharacteristic(_characteristic).listen(
_onCharacteristicChanged,
);
} else {
print("The characteristic does not support notifications or indications.");
}
}
/// Callback when the characteristic value changes.
void _onCharacteristicChanged(BleCharacteristicValue event) {
// This is where you handle the incoming data.
// The 'event' parameter contains the new characteristic value.
// Add your handling code here.
}
/// Dispose method to cancel the subscription when it's no longer needed.
void dispose() {
_characteristicValueListener?.cancel();
}
Data Conversion
The List
Example
/// Converts the [BleCharacteristicValue] instance, containing BLE characteristic values in
/// their raw, List<int> form, into more usable data structures.
void onCharacteristicChanged(BleCharacteristicValue event) {
// Get the value from the event, which is a List<int>
List<int> rawValue = event.value;
// Example conversion to String
String stringValue = utf8.decode(rawValue);
print("String Value: $stringValue");
// Example conversion to JSON
try {
Map<String, dynamic> jsonValue = json.decode(stringValue);
print("JSON Value: $jsonValue");
} catch (e) {
print("Error decoding JSON: $e");
}
// Example conversion for protobuf (assuming 'MyProto' is a generated class from your .proto file)
try {
MyProto protoValue = MyProto.fromBuffer(rawValue);
print("Protobuf Value: $protoValue");
} catch (e) {
print("Error decoding Protobuf: $e");
}
// Implement other conversions based on your application needs
}
Notes and Best Practices
Interacting with BLE devices often requires writing data to a characteristic to trigger certain actions or configure settings on the peripheral. The 'SplendidBle' provides a 'writeValue' method on the 'BleCharacteristic' class to facilitate this.
When writing to a BLE characteristic, the data typically needs to be in a byte format (List<int>
).
Depending on the characteristic's specification, this could be a simple string conversion,
serialized structured data, or even encoded protobuf objects.
Data Preparation
Before calling the writeValue
method, you must convert your data into a List
utf8.encode
from Dart's dart:convert package.Map<String, dynamic>
, you first serialize it into a string
with json.encode
, then convert it to bytes..writeToBuffer()
method on the protobuf object to obtain
a List<int>
representation.Example The example below demonstrates how to write a string to a characteristic, with error handling:
// Import the required Dart convert library.
import 'dart:convert';
// ... other code for your Flutter application ...
/// Writes a string value to the given BLE characteristic.
Future<void> writeStringToCharacteristic(String value, BleCharacteristic characteristic) async {
try {
// Convert the string to a UTF-8 encoded List<int>
List<int> bytesToWrite = utf8.encode(value);
// Write the byte data to the characteristic
await characteristic.writeValue(
value: bytesToWrite,
);
print("Successfully wrote value to the characteristic.");
} catch (e) {
// Handle any errors that occur during the write operation
print("Failed to write value to characteristic: $e");
}
}
// ... other code for your Flutter application ...
Notes and Best Practices
writeValue
operation may throw exceptions if the characteristic is not writable, the
peripheral is not connected, or the provided data is not valid. Always include error handling to
manage these cases.Communicating with BLE devices often entails reading data from a characteristic to obtain
information or status updates from the peripheral. The Splendid BLE plugin offers a convenient
readValue
method on the BleCharacteristic
class for this purpose.
When reading from a BLE characteristic, you receive the data as BleCharacteristicValue
, which
consists of a byte stream (List<int>
). Depending on the characteristic's specification,
this could represent a variety of data types.
Data Interpretation
After calling the readValue
method, you may need to convert the byte stream into a usable format
depending on your application's needs:
utf8.decode
from Dart's dart:convert
package.utf8.decode
, then parse it into a Map<String, dynamic>
with json.decode
..mergeFromBuffer()
method to deserialize the
List<int>
into a protobuf object.Example The example below shows how to read from a characteristic and handle potential errors:
import 'dart:convert';
// ... other code for your Flutter application ...
/// Reads the value from the given BLE characteristic and updates the UI state.
Future<void> readCharacteristicValue(BleCharacteristic characteristic) async {
try {
// Read the characteristic value.
BleCharacteristicValue characteristicValue = await characteristic.readValue<
BleCharacteristicValue>();
// Update the state with the new value.
setState(() {
_characteristicValue = characteristicValue;
});
// Optionally, decode the value if it's expected to be a string or other data structure.
// String stringValue = utf8.decode(characteristicValue.toList());
debugPrint('Successfully read characteristic value.');
} catch (e) {
// Handle any errors that occur during the read operation.
debugPrint('Failed to read characteristic value with exception: $e');
}
}
// ... other code for your Flutter application ...
Notes and Best Practices
readValue
operation may throw exceptions if the characteristic is not readable, the
peripheral is not connected, or other communication errors occur. It is essential to include error
handling to cover these scenarios.Properly disconnecting from a BLE device is crucial for managing resources and ensuring that the application behaves predictably. The Splendid BLE plugin simplifies this process by providing a disconnect method which can be called with the device's address.
Disconnect Process
When you no longer need to be connected to the BLE peripheral (e.g., after completing data exchange,
or when the user navigates away from the application), you should invoke the disconnect
method.
This ensures that the connection is cleanly terminated and the BLE stack does not continue to
consume power for an unnecessary connection.
Example Below is an example of how to disconnect from a BLE peripheral using the device's address:
// ... other code for your Flutter application ...
/// Disconnects from the connected BLE device.
Future<void> disconnectFromDevice(BleDevice device) async {
try {
// Invoke the disconnect method using the device's address
await _ble.disconnect(device.address);
// Handle post-disconnection logic, such as updating the UI state
setState(() {
// Update your UI or application state to reflect the disconnection
});
debugPrint('Successfully disconnected from the device.');
} catch (e) {
// Handle any errors that occur during the disconnection
debugPrint('Failed to disconnect from the device with exception: $e');
}
}
// ... other code for your Flutter application ...
Notes and Best Practices
For a detailed tutorial article, please visit https://medium.com/@Toglefritz/flutter-bluetooth-a669fcf4bb44?sk=cbbae5ffb7bd42490448c478bae6a6d7
This plugin offers detailed error messages to help you handle possible exceptions gracefully in your Flutter application.
<other details coming soon>
Prerequisites Before you begin, make sure you have the following installed:
dhttpd
is a Dart tools, which requires the Dart SDK.dart pub global activate dartdoc
.dart pub global activate dhttpd
.Generating Documentation To generate the documentation for the Splendid BLE plugin, run the following command from the root of the plugin's directory:
dart doc .
This command will process the Dart comments in the codebase and produce HTML documentation in the doc/api directory.
Hosting Documentation with dhttpd First, navigate to the directory where the documentation was generated:
cd doc/api
Then, activate the dhttpd
tool:
dart pub global activate dhttpd
Start the dhttpd server:
dhttpd --path .
By default, dhttpd
will serve files on port 8080. You can specify a different port with
the --port
argument if needed.
Accessing the Documentation
Once dhttpd
is running, open your web browser and navigate to http://localhost:8080. This will
open the locally hosted version of the documentation.
You'll be able to browse all the classes, methods, and properties of the Splendid BLE plugin, along with detailed comments and explanations as provided in the source code.
Stopping the Server
When you are done viewing the documentation, return to the terminal and press Ctrl+C
to stop
the dhttpd
server.
Contributions, suggestions, and feedback are all welcome and very much appreciated. Please open an issue or submit a pull request on the GitHub repository.
MIT License
In the creation of this Flutter Bluetooth Low Energy plugin, artificial intelligence (AI) tools have been utilized. These tools have assisted in various stages of the plugin's development, from initial code generation to the optimization of algorithms.
It is emphasized that the AI's contributions have been thoroughly overseen. Each segment of AI-assisted code has undergone meticulous scrutiny to ensure adherence to high standards of quality, reliability, and performance. This scrutiny was conducted by the sole developer responsible for the plugin's creation.
Rigorous testing has been applied to all AI-suggested outputs, encompassing a wide array of conditions and use cases. Modifications have been implemented where necessary, ensuring that the AI's contributions are well-suited to the specific requirements and limitations inherent in Bluetooth Low Energy technology.
Commitment to the plugin's accuracy and functionality is paramount, and feedback or issue reports from users are invited to facilitate continuous improvement.
It is to be understood that this plugin, like all software, is subject to evolution over time. The developer is dedicated to its progressive refinement and is actively working to surpass the expectations of the Flutter community.