import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import './bluetooth_device_list_entry.dart'; class BluetoothScreen extends StatefulWidget { /// If true, discovery starts on page start, otherwise user must press action button. final bool start; const BluetoothScreen({this.start = true}); @override _BluetoothScreen createState() => new _BluetoothScreen(); } class _BluetoothScreen extends State { StreamSubscription? _streamSubscription; List results = List.empty(growable: true); bool isDiscovering = false; _BluetoothScreen(); @override void initState() { super.initState(); isDiscovering = widget.start; if (isDiscovering) { _startDiscovery(); } } void _restartDiscovery() { setState(() { results.clear(); isDiscovering = true; }); _startDiscovery(); } void _startDiscovery() { _streamSubscription = FlutterBluetoothSerial.instance.startDiscovery().listen((r) { setState(() { final existingIndex = results.indexWhere( (element) => element.device.address == r.device.address); if (existingIndex >= 0) results[existingIndex] = r; else results.add(r); }); }); _streamSubscription!.onDone(() { setState(() { isDiscovering = false; }); }); } // @TODO . One day there should be `_pairDevice` on long tap on something... ;) @override void dispose() { // Avoid memory leak (`setState` after dispose) and cancel discovery _streamSubscription?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: isDiscovering ? Text('Discovering devices') : Text('Discovered devices'), actions: [ isDiscovering ? FittedBox( child: Container( margin: new EdgeInsets.all(16.0), child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Colors.white), ), ), ) : IconButton( icon: Icon(Icons.replay), onPressed: _restartDiscovery, ) ], ), body: ListView.builder( itemCount: results.length, itemBuilder: (BuildContext context, index) { BluetoothDiscoveryResult result = results[index]; final device = result.device; final address = device.address; return BluetoothDeviceListEntry( device: device, rssi: result.rssi, onTap: () { Navigator.of(context).pop(result.device); }, onLongPress: () async { try { bool bonded = false; if (device.isBonded) { print('Unbonding from ${device.address}...'); await FlutterBluetoothSerial.instance .removeDeviceBondWithAddress(address); print('Unbonding from ${device.address} has succed'); } else { print('Bonding with ${device.address}...'); bonded = (await FlutterBluetoothSerial.instance .bondDeviceAtAddress(address))!; print( 'Bonding with ${device.address} has ${bonded ? 'succed' : 'failed'}.'); } setState(() { results[results.indexOf(result)] = BluetoothDiscoveryResult( device: BluetoothDevice( name: device.name ?? '', address: address, type: device.type, bondState: bonded ? BluetoothBondState.bonded : BluetoothBondState.none, ), rssi: result.rssi); }); } catch (ex) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Error occured while bonding'), content: Text("${ex.toString()}"), actions: [ new TextButton( child: new Text("Close"), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); } }, ); }, ), ); } }