Search code examples
flutterdartbluetooth-lowenergy

How to bond a BLE device with a phone using Flutter and flutter_blue package?


I'm developing a Flutter application where I need to connect and bond a Bluetooth Low Energy (BLE) device with a phone. I'm using the flutter_blue package for Bluetooth connectivity. While I can scan for devices and connect to them, I'm struggling with how to handle the bonding process.

Here's a snippet of my code for scanning and connecting to devices:

import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';

class BluetoothLEManager extends StatefulWidget {
  const BluetoothLEManager({Key? key}) : super(key: key);

  @override
  BluetoothLEManagerState createState() => BluetoothLEManagerState();
}

class BluetoothLEManagerState extends State<BluetoothLEManager> with SingleTickerProviderStateMixin {
  FlutterBlue flutterBlue = FlutterBlue.instance;
  List<BluetoothDevice> _devices = [];
  bool _isSearching = false;
  bool _scanCompleted = false;

  @override
  void initState() {
    super.initState();
    startBluetoothScan(isInitialScan: true);
  }

  @override
  void dispose() {
    flutterBlue.stopScan();
    super.dispose();
  }

  void startBluetoothScan({bool isInitialScan = false}) {
    if (_isSearching) return;

    setState(() {
      _isSearching = true;
      _scanCompleted = false;
    });

    flutterBlue.startScan(timeout: const Duration(seconds: 10)).then((_) {
      setState(() {
        _isSearching = false;
        _scanCompleted = true;
      });
    });

    flutterBlue.scanResults.listen((results) {
      setState(() {
        _devices = results.map((r) => r.device).where((device) => device.name.isNotEmpty).toList();
      });
    });

    flutterBlue.isScanning.listen((isScanning) {
      if (!isScanning) {
        print('Scanning stopped');
      }
    });
  }

  Future<void> connectToDevice(BluetoothDevice device) async {
    await device.connect();
    // further code to handle connection and service discovery
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BLE Devices'),
      ),
      body: ListView.builder(
        itemCount: _devices.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(_devices[index].name),
            onTap: () => connectToDevice(_devices[index]),
          );
        },
      ),
    );
  }
}

How do I bond a BLE device with the phone using the flutter_blue package and is there a specific method or process I should follow to ensure the device is bonded before establishing a connection?

Any guidance or example code would be greatly appreciated.

(What I've tried below)

Attempt to Check Bond State and Pair:

I tried to check the bond state of the device and initiate pairing if the device was not already paired. The code snippet I used was:

Future<void> checkAndPairDevice(BluetoothDevice device) async {
  final BondState bondState = await device.bondState.first;

  if (bondState == BondState.bonded) {
    print('Device is already paired');
  } else {
    print('Device is not paired. Attempting to pair...');
    try {
      await device.pair();
      print('Pairing successful');
    } catch (error) {
      print('Pairing failed: $error');
      return;
    }
  }
}

Unfortunately, this approach did not work because the flutter_blue package does not support the bondState and pair methods directly. I encountered errors indicating these methods were undefined.

Switching to flutter_blue_plus Package:

I attempted to switch to the flutter_blue_plus package, which has more features and active maintenance. However, this resulted in numerous type conflicts and compatibility issues between flutter_blue and flutter_blue_plus types, making it impractical to integrate both packages.

Manual Pairing:

I also considered manually prompting the user to pair the devices via the system's Bluetooth settings before initiating the connection in the app. However, this approach does not provide a seamless user experience and does not ensure the app can programmatically verify and handle pairing.


Solution

  • The use of flutter_blue is deprecated due to irregular updates. Instead, we can use flutter_blue_plus, which is a fork of flutter_blue with more regular updates and minimal changes needed. Here’s how we can attempt bonding with a Bluetooth device using flutter_blue_plus:

    import 'dart:io';
    import 'package:flutter_blue_plus/flutter_blue_plus.dart';
    import 'package:device_info/device_info.dart';
    
    Future<void> attemptBonding(BluetoothDevice device) async {
      // get the device UUID
      DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
      String deviceUUID;
    
      try {
        if (Platform.isAndroid) {
          AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
          deviceUUID = androidInfo.id;
        } else if (Platform.isIOS) {
          IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
          deviceUUID = iosInfo.identifierForVendor ?? '';
        } else {
          print('Unsupported platform');
          return;
        }
      } catch (e) {
        print('Error getting device UUID: $e');
        return;
      }
    
      // convert the UUID to a byte array
      List<int> uuidBytes = deviceUUID.codeUnits;
    
      // function to match the UUID ignoring the first four characters (device name)
      bool matchUUID(String characteristicUUID, String targetUUID) {
        return characteristicUUID.substring(4).toLowerCase() == targetUUID.substring(4).toLowerCase();
      }
    
      // discover services and characteristics
      List<BluetoothService> services;
      try {
        services = await device.discoverServices();
      } catch (e) {
        print('Error discovering services: $e');
        return;
      }
    
      // find the bonding characteristic
      BluetoothCharacteristic? bondingCharacteristic;
      for (var service in services) {
        bondingCharacteristic = service.characteristics.firstWhereOrNull(
          (c) => matchUUID(c.uuid.toString(), 'UUID of your device that returns bonding characteristic')
        );
        if (bondingCharacteristic != null) break;
      }
    
      if (bondingCharacteristic != null) {
        try {
          print('Attempting to write to bonding characteristic...');
          await bondingCharacteristic.write(uuidBytes, withoutResponse: true);
          print('Written UUID to bonding characteristic.');
        } catch (e) {
          print('Error writing to bonding characteristic: $e');
          return;
        }
      } else {
        print('Bonding characteristic not found');
        return;
      }
    
      // check bonding status
      BluetoothCharacteristic? bondingStatusCharacteristic;
      for (var service in services) {
        bondingStatusCharacteristic = service.characteristics.firstWhereOrNull(
          (c) => matchUUID(c.uuid.toString(), 'UUID of your device that returns bonding status characteristic')
        );
        if (bondingStatusCharacteristic != null) break;
      }
    
      if (bondingStatusCharacteristic != null) {
        try {
          await Future.delayed(Duration(seconds: 1)); // add a delay to ensure the bonding is processed
          List<int> bondingStatus = await bondingStatusCharacteristic.read();
          print('Bonding status value: $bondingStatus');
          if (bondingStatus.isNotEmpty && bondingStatus[0] == 1) {
            print('Bonding successful.');
          } else {
            print('Bonding failed or not recognized by the device.');
          }
        } catch (e) {
          print('Error reading bonding status: $e');
        }
      } else {
        print('Bonding status characteristic not found');
      }
    }
    

    This approach ensures better compatibility and regular updates by using flutter_blue_plus, making the Bluetooth bonding process more reliable.