Introduction
Building on our previous discussion in Part 3 of this series, where we established the basics of BLE communication using Flutter, this blog post will dive deeper into advanced BLE operations. We’ll explore managing connections with robust reconnection strategies, handling connection timeouts, and ensuring a stable communication flow with BLE devices using the flutter_blue_plus package.
Learn more about Mobile App Development Services
Managing BLE Connections
Maintaining a stable connection with a BLE device is critical for ensuring seamless interaction. It’s not just about connecting; it’s about ensuring that the connection stays alive and is responsive to both the app and the device’s needs.
Reconnecting After Disconnection:
One of the most important aspects of BLE communication is handling unexpected disconnections. BLE devices can disconnect for various reasons – low battery, going out of range, or interference. A robust BLE application should automatically attempt to reconnect when this happens.
Here’s how to implement automatic reconnection:
class AdvancedBLEConnectionManager {
static final Map<String, int> _reconnectionAttempts = {};
static const int _maxReconnectionAttempts = 3;
static const Duration _reconnectionDelay = Duration(seconds: 5);
static Future<bool> connectWithReconnection(BluetoothDevice device) async {
try {
// Connect to device
await device.connect();
// Setup connection monitoring for reconnection
_setupConnectionMonitoring(device);
// Reset reconnection attempts on successful connection
_reconnectionAttempts[device.remoteId.toString()] = 0;
return true;
} catch (e) {
print(‘Connection failed: $e’);
return false;
}
}
static void _setupConnectionMonitoring(BluetoothDevice device) {
device.connectionState.listen((state) {
if (state == BluetoothConnectionState.disconnected) {
print(‘Device disconnected – attempting reconnection’);
_handleDisconnection(device);
} else if (state == BluetoothConnectionState.connected) {
print(‘Device reconnected successfully’);
_reconnectionAttempts[device.remoteId.toString()] = 0;
}
});
}
static void _handleDisconnection(BluetoothDevice device) {
final deviceId = device.remoteId.toString();
final attempts = _reconnectionAttempts[deviceId] ?? 0;
if (attempts < _maxReconnectionAttempts) {
print(‘Attempting reconnection ${attempts + 1}/$_maxReconnectionAttempts’);
Timer(_reconnectionDelay, () async {
await _attemptReconnection(device);
});
} else {
print(‘Max reconnection attempts reached’);
}
}
static Future<void> _attemptReconnection(BluetoothDevice device) async {
final deviceId = device.remoteId.toString();
_reconnectionAttempts[deviceId] = (_reconnectionAttempts[deviceId] ?? 0) + 1;
try {
await device.connect();
print(‘Reconnection successful’);
} catch (e) {
print(‘Reconnection failed: $e’);
_handleDisconnection(device); // Try again
}
}
}
This snippet listens for changes in the device connection state. If the device gets disconnected, it automatically attempts to reconnect up to a maximum number of times with a delay between attempts.
Handling Connection Timeouts:
Connection timeouts are essential for preventing your app from hanging indefinitely when trying to connect to a device that may be unavailable.
static Future<bool> connectWithTimeout(BluetoothDevice device, Duration timeout) async {
try {
await device.connect(timeout: timeout);
return true;
} on TimeoutException {
print(‘Connection timeout for ${device.advName}’);
return false;
} catch (e) {
print(‘Connection failed: $e’);
return false;
}
}
The connect method can accept a timeout parameter. This ensures that if the device doesn’t connect within the specified time frame, the app can handle the situation gracefully.
Handling Notifications
Subscribing to Notifications:
Enhanced notification subscription with proper error handling:
static Future<bool> subscribeToNotifications(
BluetoothDevice device,
BluetoothCharacteristic characteristic,
Function(List<int>) onDataReceived,
) async {
try {
if (!characteristic.properties.notify && !characteristic.properties.indicate) {
throw Exception(‘Characteristic does not support notifications’);
}
// Subscribe to notifications
final subscription = characteristic.onValueReceived.listen((value) {
print(‘Notification received: $value’);
onDataReceived(value);
});
// Cleanup subscription when device disconnects
device.cancelWhenDisconnected(subscription);
// Enable notifications
await characteristic.setNotifyValue(true);
print(‘Successfully subscribed to notifications’);
return true;
} catch (e) {
print(‘Error subscribing to notifications: $e’);
return false;
}
}
Here we subscribe to notifications from a characteristic. When the characteristic value changes, the app gets notified and can handle the new data accordingly. The subscription is automatically cancelled when the device disconnects.
Unsubscribing from Notifications:
When notifications are no longer needed, it’s important to unsubscribe to save power and reduce unnecessary data transmission.
static Future<void> unsubscribeFromNotifications(
BluetoothDevice device,
BluetoothCharacteristic characteristic,
) async {
try {
// Disable notifications
await characteristic.setNotifyValue(false);
print(‘Successfully unsubscribed from notifications’);
} catch (e) {
print(‘Error unsubscribing from notifications: $e’);
}
}
Reading and Writing Characteristics with Error Handling
While we covered the basic read and write operations in Part 3, handling potential errors is crucial for robust applications.
Reading Characteristics with Try-Catch:
static Future<List<int>> readCharacteristicWithErrorHandling(
BluetoothCharacteristic characteristic
) async {
try {
if (!characteristic.properties.read) {
throw Exception(‘Characteristic is not readable’);
}
List<int> value = await characteristic.read();
print(‘Successfully read: $value’);
return value;
} catch (e) {
print(‘Error reading characteristic: $e’);
return [];
}
}
A try-catch block ensures that any issues during the read operation are caught and managed without crashing the app.
Writing Characteristics with Acknowledgment:
static Future<bool> writeCharacteristicWithAcknowledgment(
BluetoothCharacteristic characteristic,
List<int> value
) async {
try {
if (!characteristic.properties.write && !characteristic.properties.writeWithoutResponse) {
throw Exception(‘Characteristic is not writable’);
}
// Write with response (acknowledgment)
await characteristic.write(value, withoutResponse: false);
print(‘Successfully wrote with acknowledgment: $value’);
return true;
} catch (e) {
print(‘Error writing characteristic: $e’);
return false;
}
}
The withoutResponse flag set to false means the app waits for an acknowledgment from the device that the write operation was successful.
Complete Example Implementation
Here’s how to integrate all these advanced features into a complete BLE management system:
class EnhancedBLEManager {
static Future<void> setupAdvancedConnection(BluetoothDevice device) async {
// Connect with reconnection management
final connected = await AdvancedBLEConnectionManager.connectWithReconnection(device);
if (connected) {
// Discover services
final services = await device.discoverServices();
// Setup notifications for all notifiable characteristics
for (final service in services) {
for (final characteristic in service.characteristics) {
if (characteristic.properties.notify || characteristic.properties.indicate) {
await subscribeToNotifications(device, characteristic, (data) {
print(‘Received notification: $data’);
// Handle notification data
});
}
}
}
}
}
static Future<void> performDataOperations(BluetoothDevice device) async {
try {
final services = await device.discoverServices();
for (final service in services) {
for (final characteristic in service.characteristics) {
// Read from readable characteristics
if (characteristic.properties.read) {
final data = await readCharacteristicWithErrorHandling(characteristic);
print(‘Read data: $data’);
}
// Write to writable characteristics
if (characteristic.properties.write) {
final success = await writeCharacteristicWithAcknowledgment(
characteristic,
[0x01, 0x02, 0x03]
);
print(‘Write success: $success’);
}
}
}
} catch (e) {
print(‘Error performing data operations: $e’);
}
}
}
Best Practices for Advanced BLE Operations
1. Always Handle Disconnections Gracefully:
device.connectionState.listen((state) {
switch (state) {
case BluetoothConnectionState.connected:
print(‘Connected – ready for operations’);
break;
case BluetoothConnectionState.disconnected:
print(‘Disconnected – cleaning up resources’);
// Cleanup subscriptions and attempt reconnection
break;
default:
print(‘Connection state: $state’);
}
});
2. Use Proper Error Handling:
Future<bool> safeOperation(Function operation) async {
try {
await operation();
return true;
} on TimeoutException {
print(‘Operation timed out’);
return false;
} on PlatformException catch (e) {
print(‘Platform error: ${e.message}’);
return false;
} catch (e) {
print(‘Unexpected error: $e’);
return false;
}
}
3. Clean Up Resources:
static void cleanupResources() {
// Cancel all subscriptions
for (var subscription in subscriptions) {
subscription.cancel();
}
// Clear reconnection timers
for (var timer in reconnectionTimers) {
timer.cancel();
}
print(‘All resources cleaned up’);
}
Want a Seamless BLE Implementation for Flutter Apps?
Delve deeper into connecting BLE devices with Flutter. If you are ready to implement these insights in your app, connect with AlphaBOLD for custom development expert guidance and seamless BLE implementation.
Request a consultationConclusion
In this installment of our series, we have taken our BLE operations to the next level, integrating advanced techniques for managing connections and handling notifications. These practices are essential for creating a stable and reliable communication channel between your Flutter app and BLE devices.
Key improvements covered in this part include:
- Automatic Reconnection: Robust reconnection strategies with retry limits and delays
- Connection Timeout Handling: Preventing indefinite connection attempts
- Enhanced Notification Management: Proper subscription and unsubscription with error handling
- Error Handling: Comprehensive try-catch blocks for all BLE operations
- Write Acknowledgments: Ensuring write operations are confirmed by the device
As always, we encourage you to experiment with these snippets and integrate them into your application logic. In our next post, we will look at troubleshooting common BLE issues and optimizing the BLE communication for better performance and user experience.
Stay tuned, and keep coding! If you have any questions or topics you would like us to cover, feel free to reach out. Your feedback drives our content. Thank you for following along in our BLE with Flutter series!
Explore Recent Blog Posts








