首页 > 解决方案 > Dart 和 Flutter 异步初始化最佳实践和代码结构 - 如何在继续之前等待流中的某个条件

问题描述

我正在努力在 dart/flutter 应用程序中初始化 BLE 连接。代码库来自修改这个使用flutter_reactive_ble 库的示例项目

我是异步编程的新手,并且在进入初始化的下一步之前一直在努力使用正确/简洁的方法来等待函数完成,因为所有函数都返回 void。

当我尝试创建函数 Futures 时,我无法弄清楚如何正确地将其应用于 Stream,因为它返回 Null 并在我返回 _startScan() 的结果之前继续下一步。

当前代码有效但很混乱,绝对不是最佳实践:

void initState() {
  super.initState();
  _dataToSendText = TextEditingController();
  initBle();
}

void initBle() async {
print('starting scan');
_startScan();

while (true) {
  if (_foundBleUARTDevices.isNotEmpty) {
    print('stopping scan');
    _stopScan();
    break;
  } else {
    print('waiting 3 sec for start scan to complete');
    await Future.delayed(Duration(seconds: 1), () {});
  }
}

while (true) {
  if (!_scanning) {
    print('connecting to module');
    onConnectDevice(0);
    break;
  } else {
    print('waiting  1 sec for Stop scan to complete');
    await Future.delayed(Duration(seconds: 1), () {});
  }
}

while (true) {
  if (_connected) {
    break;
  } else {
    print('waiting 1 sec to connect to module');
    await Future.delayed(Duration(seconds: 3), () {});
  }
}

print('sending first data packet)');
_sendData('18EF9697080100F1FFFE010101');
}

如果我能弄清楚如何在被调用的函数中正确返回 Futures,我猜代码应该看起来像这样

void initBle() async {
  print('starting scan');
  await _startScan();
  print('stopping scan');
  await _stopScan();
  print('connecting to module');
  await onConnectDevice(0);

  print('sending first data packet)');
  _sendData('18EF9697080100F1FFFE010101');
}

或者也许有更好的方法来完全完成这个 BLE 初始化序列?我是 Flutter 的新手,对任何最佳实践持开放态度。

完整代码在这里:

    import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:location_permissions/location_permissions.dart';
import 'dart:io' show Platform;

// This flutter app demonstrates an usage of the flutter_reactive_ble flutter plugin
// This app works only with BLE devices which advertise with a Nordic UART Service (NUS) UUID
Uuid _UART_UUID = Uuid.parse("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
Uuid _UART_RX = Uuid.parse("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
Uuid _UART_TX = Uuid.parse("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
// Uuid _UART_UUID = Uuid.parse("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
// Uuid _UART_RX   = Uuid.parse("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
// Uuid _UART_TX   = Uuid.parse("6e400003-b5a3-f393-e0a9-e50e24dcca9e");

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter_reactive_ble example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter_reactive_ble UART example'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  final flutterReactiveBle = FlutterReactiveBle();
  List<DiscoveredDevice> _foundBleUARTDevices = [];
  StreamSubscription<DiscoveredDevice> _scanStream;
  Stream<ConnectionStateUpdate> _currentConnectionStream;
  StreamSubscription<ConnectionStateUpdate> _connection;
  QualifiedCharacteristic _txCharacteristic;
  QualifiedCharacteristic _rxCharacteristic;
  Stream<List<int>> _receivedDataStream;
  TextEditingController _dataToSendText;
  bool _scanning = false;
  bool _connected = false;
  String _logTexts = "";
  List<String> _receivedData = [];
  int _numberOfMessagesReceived = 0;

  void initState() {
    super.initState();
    _dataToSendText = TextEditingController();
    initBle();
  }

  void initBle() async {
    print('starting scan');
    _startScan();

    while (true) {
      if (_foundBleUARTDevices.isNotEmpty) {
        print('stopping scan');
        _stopScan();
        break;
      } else {
        print('waiting 3 sec for start scan to complete');
        await Future.delayed(Duration(seconds: 1), () {});
      }
    }

    while (true) {
      if (!_scanning) {
        print('connecting to module');
        onConnectDevice(0);
        break;
      } else {
        print('waiting  1 sec for Stop scan to complete');
        await Future.delayed(Duration(seconds: 1), () {});
      }
    }

    while (true) {
      if (_connected) {
        break;
      } else {
        print('waiting 1 sec to connect to module');
        await Future.delayed(Duration(seconds: 3), () {});
      }
    }

    print('sending first data packet)');
    _sendData('18EF9697080100F1FFFE010101');
  }

  // void initBle() async {
  //   print('starting scan');
  //   await _startScan();
  //   print('stopping scan');
  //   await _stopScan();
  //   print('connecting to module');
  //   await onConnectDevice(0);
  //
  //   print('sending first data packet)');
  //   _sendData('18EF9697080100F1FFFE010101');
  // }

  void refreshScreen() {
    setState(() {});
  }

  Future<void> _sendUserInputData() async {
    await flutterReactiveBle.writeCharacteristicWithResponse(_rxCharacteristic,
        value: _dataToSendText.text.codeUnits);
  }

  void _sendData(String data) async {
    List<int> value = utf8.encode(data);
    await flutterReactiveBle.writeCharacteristicWithResponse(_rxCharacteristic,
        value: value);
  }

  void onNewReceivedData(List<int> data) {
    print("data = $data");
    for (int i in data) {
      print(i.toRadixString(16));
    }
    _numberOfMessagesReceived += 1;
    _receivedData
        .add("$_numberOfMessagesReceived: ${String.fromCharCodes(data)}");
    if (_receivedData.length > 5) {
      _receivedData.removeAt(0);
    }
    refreshScreen();
  }

  void _disconnect() async {
    await _connection.cancel();
    _connected = false;
    refreshScreen();
  }

  void _stopScan() async {
    await _scanStream.cancel();
    _scanning = false;
    refreshScreen();
  }

  Future<void> showNoPermissionDialog() async => showDialog<void>(
        context: context,
        barrierDismissible: false, // user must tap button!
        builder: (BuildContext context) => AlertDialog(
          title: const Text('No location permission '),
          content: SingleChildScrollView(
            child: ListBody(
              children: <Widget>[
                const Text('No location permission granted.'),
                const Text(
                    'Location permission is required for BLE to function.'),
              ],
            ),
          ),
          actions: <Widget>[
            TextButton(
              child: const Text('Acknowledge'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        ),
      );

  void _startScan() async {
    bool goForIt = false;
    PermissionStatus permission;
    if (Platform.isAndroid) {
      permission = await LocationPermissions().requestPermissions();
      if (permission == PermissionStatus.granted) goForIt = true;
    } else if (Platform.isIOS) {
      goForIt = true;
    }

    if (goForIt) {
      //TODO replace True with permission == PermissionStatus.granted is for IOS test
      _foundBleUARTDevices = [];
      _scanning = true;
      refreshScreen();
      _scanStream = flutterReactiveBle
          .scanForDevices(withServices: [_UART_UUID]).listen((device) {
        if (_foundBleUARTDevices.every((element) => element.id != device.id)) {
          print("found $device");
          _foundBleUARTDevices.add(device);
          refreshScreen();
        }
      }, onError: (Object error) {
        _logTexts = "${_logTexts}ERROR while scanning:$error \n";
        refreshScreen();
      });
    } else {
      await showNoPermissionDialog();
    }
  }

  void onConnectDevice(index) async {
    _currentConnectionStream = flutterReactiveBle.connectToAdvertisingDevice(
      id: _foundBleUARTDevices[index].id,
      prescanDuration: Duration(seconds: 1),
      withServices: [_UART_UUID, _UART_RX, _UART_TX],
    );
    _logTexts = "";
    refreshScreen();
    _connection = _currentConnectionStream.listen((event) {
      var id = event.deviceId.toString();
      switch (event.connectionState) {
        case DeviceConnectionState.connecting:
          {
            _logTexts = "${_logTexts}Connecting to $id\n";
            break;
          }
        case DeviceConnectionState.connected:
          {
            _logTexts = "${_logTexts}Connected to $id\n";
            _numberOfMessagesReceived = 0;
            _receivedData = [];
            _txCharacteristic = QualifiedCharacteristic(
                serviceId: _UART_UUID,
                characteristicId: _UART_TX,
                deviceId: event.deviceId);
            _receivedDataStream =
                flutterReactiveBle.subscribeToCharacteristic(_txCharacteristic);
            _receivedDataStream.listen((data) {
              onNewReceivedData(data);
            }, onError: (dynamic error) {
              _logTexts = "${_logTexts}Error:$error$id\n";
            });
            _rxCharacteristic = QualifiedCharacteristic(
                serviceId: _UART_UUID,
                characteristicId: _UART_RX,
                deviceId: event.deviceId);
            _connected = true;

            break;
          }
        case DeviceConnectionState.disconnecting:
          {
            _connected = false;
            _logTexts = "${_logTexts}Disconnecting from $id\n";
            break;
          }
        case DeviceConnectionState.disconnected:
          {
            _logTexts = "${_logTexts}Disconnected from $id\n";
            break;
          }
      }
      refreshScreen();
    });
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: SingleChildScrollView(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              const Text("BLE UART Devices found:"),
              Container(
                  margin: const EdgeInsets.all(3.0),
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(10),
                      border: Border.all(color: Colors.blue, width: 2)),
                  height: 100,
                  child: ListView.builder(
                      itemCount: _foundBleUARTDevices.length,
                      itemBuilder: (context, index) => Card(
                              child: ListTile(
                            dense: true,
                            enabled: !((!_connected && _scanning) ||
                                (!_scanning && _connected)),
                            trailing: GestureDetector(
                              behavior: HitTestBehavior.translucent,
                              onTap: () {
                                (!_connected && _scanning) ||
                                        (!_scanning && _connected)
                                    ? () {}
                                    : onConnectDevice(index);
                              },
                              child: Container(
                                width: 48,
                                height: 48,
                                padding:
                                    const EdgeInsets.symmetric(vertical: 4.0),
                                alignment: Alignment.center,
                                child: const Icon(Icons.add_link),
                              ),
                            ),
                            subtitle: Text(_foundBleUARTDevices[index].id),
                            title: Text(
                                "$index: ${_foundBleUARTDevices[index].name}"),
                          )))),
              const Text("Status messages:"),
              Container(
                  margin: const EdgeInsets.all(3.0),
                  width: 1400,
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(10),
                      border: Border.all(color: Colors.blue, width: 2)),
                  height: 90,
                  child: Scrollbar(
                      child: SingleChildScrollView(child: Text(_logTexts)))),
              const Text("Received data:"),
              Container(
                  margin: const EdgeInsets.all(3.0),
                  width: 1400,
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(10),
                      border: Border.all(color: Colors.blue, width: 2)),
                  height: 90,
                  child: Text(_receivedData.join("\n"))),
              const Text("Send message:"),
              Container(
                  margin: const EdgeInsets.all(3.0),
                  padding: const EdgeInsets.all(8.0),
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(10),
                      border: Border.all(color: Colors.blue, width: 2)),
                  child: Row(children: <Widget>[
                    Expanded(
                        child: TextField(
                      enabled: _connected,
                      controller: _dataToSendText,
                      decoration: const InputDecoration(
                          border: InputBorder.none, hintText: 'Enter a string'),
                    )),
                    RaisedButton(
                        child: Icon(
                          Icons.send,
                          color: _connected ? Colors.blue : Colors.grey,
                        ),
                        onPressed: _connected ? _sendUserInputData : () {}),
                  ]))
            ],
          ),
        ),
        persistentFooterButtons: [
          Container(
            height: 35,
            child: Column(
              children: [
                if (_scanning)
                  const Text("Scanning: Scanning")
                else
                  const Text("Scanning: Idle"),
                if (_connected)
                  const Text("Connected")
                else
                  const Text("disconnected."),
              ],
            ),
          ),
          RaisedButton(
            onPressed: !_scanning && !_connected ? _startScan : () {},
            child: Icon(
              Icons.play_arrow,
              color: !_scanning && !_connected ? Colors.blue : Colors.grey,
            ),
          ),
          RaisedButton(
              onPressed: _scanning ? _stopScan : () {},
              child: Icon(
                Icons.stop,
                color: _scanning ? Colors.blue : Colors.grey,
              )),
          RaisedButton(
              onPressed: _connected ? _disconnect : () {},
              child: Icon(
                Icons.cancel,
                color: _connected ? Colors.blue : Colors.grey,
              ))
        ],
      );
}

标签: flutterdartasynchronousasync-awaitstream

解决方案


推荐阅读