flutter - 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,
))
],
);
}
解决方案
推荐阅读
- r - 多个日期之间的日期向量
- java - 有没有办法使用 Spring 注入 Tomcat 服务器配置?
- server - 自动化发布 tableau 仪表板的过程
- kubernetes-helm - Helm Chart - 合并具有不同结构的两个值
- node.js - 使用 Cloudbuild 部署到 App Engine 失败
- r - 按组过滤小标题
- c - 如何比较 2 个单链表是否包含相同的数据(无论顺序如何)?
- flutter - 如何使用 HIVE、Flutter 为两个不同的 Modal 类注册两个适配器?
- sql - 选择指定时间段内每个日期的最后记录值
- makefile - Make:是否可以从模式匹配中构建依赖关系?