Connecting BLE Devices with Flutter (Part 2) – Bluetooth State

Overview

If you’re looking to connect a Bluetooth Low Energy (BLE) device to your Flutter app, then using the flutter_blue_plus package is a great way to do it. Previously, we discussed how to handle the Bluetooth state of your mobile device and perform operations based on changing of the Bluetooth state. In this blog, we’ll go over how to use the flutter_blue_plus package to scan for BLE devices, connect to them, and provide some updated code snippets to help you get started.

Implementation

To use the flutter_blue_plus package, you’ll first need to add it to your pubspec.yaml file. You can do this by adding the following line under the dependencies section:

dependencies:

flutter:

sdk: flutter

flutter_blue_plus: ^1.35.5

Next, you’ll need to import the package 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:

// No need to create an instance – use static methods directly

// FlutterBluePlus.startScan(), FlutterBluePlus.scanResults, etc.

Scanning for BLE Devices

Once we have the package imported, we can start scanning for BLE devices. The process involves starting a scan and listening to the results through a stream. Here’s how to implement device scanning:

Basic Scanning Implementation:

class BLEScanner {

static List<ScanResult> scanResults = [];

static StreamSubscription<List<ScanResult>>? scanSubscription;

static bool isScanning = false;

 

static Future<void> startScan() async {

if (isScanning) return;

 

try {

isScanning = true;

scanResults.clear();

 

// Listen to scan results

scanSubscription = FlutterBluePlus.scanResults.listen((results) {

scanResults = results;

// Process each discovered device

for (ScanResult result in results) {

print(‘Device found: ${result.device.advName} – ${result.device.remoteId}’);

}

});

 

// Start scanning with timeout

await FlutterBluePlus.startScan(

timeout: const Duration(seconds: 15),

androidUsesFineLocation: true,

);

} catch (e) {

print(‘Error starting scan: $e’);

} finally {

isScanning = false;

}

}

 

static Future<void> stopScan() async {

await FlutterBluePlus.stopScan();

scanSubscription?.cancel();

isScanning = false;

}

}

Advanced Scanning with Filters:

flutter_blue_plus provides various filtering options to help you find specific devices more efficiently:

static Future<void> startScanWithFilters() async {

  try {

    // Listen to scan results

    scanSubscription = FlutterBluePlus.scanResults.listen((results) {

      for (ScanResult result in results) {

        print(‘Filtered device: ${result.device.advName} – RSSI: ${result.rssi}’);

      }

    });

 

    // Start scanning with filters

    await FlutterBluePlus.startScan(

      timeout: const Duration(seconds: 15),

      withServices: [Guid(“180D”)], // Filter by Heart Rate Service

      withNames: [“MyDevice”, “SensorHub”], // Filter by device names

      androidUsesFineLocation: true,

    );

  } catch (e) {

    print(‘Error starting filtered scan: $e’);

  }

}

Finding a Specific Device:

Once you have the list of ScanResult objects, you can iterate through the list and find the device you want to connect to. Here’s an updated approach:

static ScanResult? findTargetDevice(String targetDeviceName) {

  for (ScanResult result in scanResults) {

    // Check device name (advertising name)

    if (result.device.advName == targetDeviceName) {

      return result;

    }

    // Alternative: check by remote ID

    if (result.device.remoteId.toString() == targetDeviceName) {

      return result;

    }

  }

  return null;

}

Connecting to BLE Devices

Once you have found the device you want to connect to, you can call the connect method on the BluetoothDevice object. The connection process in flutter_blue_plus is more robust with better error handling:

class BLEConnectionManager {

static BluetoothDevice? connectedDevice;

static StreamSubscription<BluetoothConnectionState>? connectionSubscription;

static Future<bool> connectToDevice(BluetoothDevice device) async {

try {

// Connect to the device with timeout

await device.connect(

timeout: const Duration(seconds: 35),

autoConnect: false,

);

// Listen to connection state changes

connectionSubscription = device.connectionState.listen((state) {

print(‘Connection State: $state’);

switch (state) {

case BluetoothConnectionState.connected:

connectedDevice = device;

print(‘Successfully connected to ${device.advName}’);

break;

case BluetoothConnectionState.disconnected:

connectedDevice = null;

print(‘Disconnected from ${device.advName}’);

break;

case BluetoothConnectionState.connecting:

print(‘Connecting to ${device.advName}…’);

break;

case BluetoothConnectionState.disconnecting:

print(‘Disconnecting from ${device.advName}…’);

break;

}

});

return true;

} catch (e) {

print(‘Error connecting to device: $e’);

return false;

}

}

static Future<void> disconnect() async {

if (connectedDevice != null) {

try {

await connectedDevice!.disconnect();

connectionSubscription?.cancel();

connectedDevice = null;

} catch (e) {

print(‘Error disconnecting: $e’);

}

}

}

}

Complete Example Implementation

Here’s a complete example showing how to integrate scanning and connection in a Flutter widget:

class BLEDeviceScreen extends StatefulWidget {

  const BLEDeviceScreen({Key? key}) : super(key: key);

  @override

  State<BLEDeviceScreen> createState() => _BLEDeviceScreenState();

}

class _BLEDeviceScreenState extends State<BLEDeviceScreen> {

  List<ScanResult> scanResults = [];

  BluetoothDevice? connectedDevice;

  bool isScanning = false;

  StreamSubscription<List<ScanResult>>? scanSubscription;

  StreamSubscription<BluetoothConnectionState>? connectionSubscription;

  @override

  void initState() {

    super.initState();

    _initializeBLE();

  }

  void _initializeBLE() {

    // Listen to scan results

    scanSubscription = FlutterBluePlus.scanResults.listen((results) {

      if (mounted) {

        setState(() {

          scanResults = results;

        });

      }

    });

  }

  Future<void> _startScan() async {

    if (isScanning) return;

    setState(() {

      isScanning = true;

      scanResults.clear();

    });

    try {

      await FlutterBluePlus.startScan(

        timeout: const Duration(seconds: 15),

        androidUsesFineLocation: true,

      );

    } catch (e) {

      print(‘Error starting scan: $e’);

    }

    setState(() {

      isScanning = false;

    });

  }

  Future<void> _connectToDevice(BluetoothDevice device) async {

    try {

      await device.connect(timeout: const Duration(seconds: 35));

      connectionSubscription = device.connectionState.listen((state) {

        if (mounted) {

          setState(() {

            connectedDevice = (state == BluetoothConnectionState.connected) ? device : null;

          });

        }

      });

    } catch (e) {

      print(‘Error connecting: $e’);

    }

  }

  @override

  void dispose() {

    scanSubscription?.cancel();

    connectionSubscription?.cancel();

    super.dispose();

  }

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: const Text(‘BLE Devices’),

        actions: [

          IconButton(

            icon: Icon(isScanning ? Icons.stop : Icons.search),

            onPressed: isScanning ? null : _startScan,

          ),

        ],

      ),

      body: Column(

        children: [

          // Connection Status

          if (connectedDevice != null)

            Container(

              padding: const EdgeInsets.all(16),

              color: Colors.green,

              width: double.infinity,

              child: Text(

                ‘Connected to: ${connectedDevice!.advName}’,

                style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),

              ),

            ),

          // Scan Button

          Padding(

            padding: const EdgeInsets.all(16),

            child: ElevatedButton(

              onPressed: isScanning ? null : _startScan,

              child: Text(isScanning ? ‘Scanning…’ : ‘Start Scan’),

            ),

          ),

          // Device List

          Expanded(

            child: scanResults.isEmpty

                ? const Center(child: Text(‘No devices found’))

                : ListView.builder(

                    itemCount: scanResults.length,

                    itemBuilder: (context, index) {

                      final result = scanResults[index];

                      final device = result.device;

                      return ListTile(

                        leading: const Icon(Icons.bluetooth),

                        title: Text(device.advName.isNotEmpty ? device.advName : device.remoteId.toString()),

                        subtitle: Text(‘RSSI: ${result.rssi}’),

                        trailing: connectedDevice?.remoteId == device.remoteId

                            ? const Icon(Icons.check_circle, color: Colors.green)

                            : ElevatedButton(

                                onPressed: () => _connectToDevice(device),

                                child: const Text(‘Connect’),

                              ),

                      );

                    },

                  ),

          ),

        ],

      ),

    );

  }

}

Basic Device Interaction

Now that you’re connected to the BLE device, you can start interacting with it. Here’s a preview of basic operations (we’ll cover these in detail in the next parts):

class BLEDeviceInteraction {

  static Future<List<BluetoothService>> discoverServices(BluetoothDevice device) async {

    try {

      return await device.discoverServices();

    } catch (e) {

      print(‘Error discovering services: $e’);

      return [];

    }

  }

 

  static Future<List<int>> readCharacteristic(BluetoothCharacteristic characteristic) async {

    try {

      return await characteristic.read();

    } catch (e) {

      print(‘Error reading characteristic: $e’);

      return [];

    }

  }

 

  static Future<void> writeCharacteristic(BluetoothCharacteristic characteristic, List<int> value) async {

    try {

      await characteristic.write(value);

    } catch (e) {

      print(‘Error writing characteristic: $e’);

    }

  }

}

Error Handling and Best Practices

When working with BLE connections, it’s important to handle errors gracefully:

class BLEErrorHandler {

  static Future<bool> handleConnection(BluetoothDevice device) async {

    try {

      await device.connect(timeout: const Duration(seconds: 35));

      return true;

    } on TimeoutException {

      print(‘Connection timeout – device may be out of range’);

      return false;

    } on PlatformException catch (e) {

      print(‘Platform exception: ${e.message}’);

      return false;

    } catch (e) {

      print(‘Unexpected error: $e’);

      return false;

    }

  }

 

  static Future<void> safeScan() async {

    try {

      await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));

    } on PlatformException catch (e) {

      if (e.code == ‘scan_too_frequent’) {

        print(‘Scanning too frequently – waiting before retry’);

        await Future.delayed(const Duration(seconds: 2));

      }

    }

  }

}

Seamless BLE Implementation for Flutter Apps

Delve deeper into connecting BLE devices with Flutter. Ready to implement these insights in your app? Connect with AlphaBOLD for custom development expert guidance and seamless BLE implementation.

Request a Consultation

Conclusion

In conclusion, connecting to Bluetooth Low Energy devices in Flutter using the latest flutter_blue_plus package is a straightforward process that provides robust functionality for BLE operations. Whether you’re building a mobile app for iOS or Android, the Flutter framework combined with flutter_blue_plus provides all the tools you need to easily scan for, connect to, and interact with BLE devices.

Key improvements in this updated version include:

  • Modern flutter_blue_plus 1.35.5 API usage
  • Proper stream-based scanning implementation
  • Enhanced connection management with state monitoring
  • Better error handling and timeout management
  • Improved filtering and device discovery
  • More robust code structure and organization

By using the updated flutter_blue_plus package, you can take advantage of improved stability, better performance, and enhanced feature set. The package handles the complexity of BLE communication while providing a clean, easy-to-use API that allows you to focus on building your app’s unique features and functionality.

In the next part of our series, we’ll dive deeper into BLE communication, covering service discovery, characteristic operations, and advanced data handling techniques.

We hope you found our article informative and comprehensive, and hope you stick around for more. We hope to see you again soon 😉.

Explore Recent Blog Posts