Flutter 将数据从 class 传递到小部件变量的最佳方式
Flutter Best way to Pass data from a class to a widget variable
我正在使用 Speech_to_text 包 将语音识别结果存储在一个字符串变量中,我以后可以将其用于不同的目的,到目前为止,我只是想要在屏幕上显示字符串。我想实现类似于 Whatsaap 录音的功能,所以我有 GestureDetector
,onLongPress
开始录音,onLongPressUp
停止录音。
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
//Variable to show on screen
String text = 'Press the button and start speaking';
bool isListening = false;
final SpeechToText speech = SpeechToText();
String lastError = "";
String lastStatus = "";
String _currentLocaleId = "";
@override
void initState() {
super.initState();
initSpeechState();
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(MyApp.title),
centerTitle: true,
),
body: SingleChildScrollView(
reverse: true,
padding: const EdgeInsets.all(30).copyWith(bottom: 150),
child: SubstringHighlight(
text: text,
terms: Command.all,
textStyle: TextStyle(
fontSize: 32.0,
color: Colors.black,
fontWeight: FontWeight.w400,
),
textStyleHighlight: TextStyle(
fontSize: 32.0,
color: Colors.red,
fontWeight: FontWeight.w400,
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: AvatarGlow(
animate: isListening,
endRadius: 75,
glowColor: Theme.of(context).primaryColor,
child: GestureDetector(
child: FloatingActionButton(
child: Icon(isListening ? Icons.mic : Icons.mic_none, size: 36),
/*onPressed: () {
toggleRecording();
print(text);
}*/
onPressed: () {},
),
onLongPress: () {
setState(() async {
text = await Speech.startListening();
});
// print(text);
},
onLongPressUp: Speech.stopListening,
//sendMessage(),
),
),
);
Future<void> initSpeechState() async {
bool hasSpeech = await speech.initialize(
onError: errorListener, onStatus: statusListener, debugLogging: false);
}
//Speech to text methods
Future<void> errorListener(SpeechRecognitionError error) async {
print("Received error status: $error, listening: ${speech.isListening}");
if (mounted) {
setState(() {
lastError = "${error.errorMsg} - ${error.permanent}";
print(lastError);
});
}
}
void statusListener(String status) {
setState(() {
print(status);
lastStatus = "$status";
});
}
}
OnLongPress
和 OnLongPressUp
分别调用方法 startListening
和 stopListening
,它们在不同的 class.
中
class Speech {
static final _speech = SpeechToText();
static String lastWords;
void cancelListening() {
_speech.cancel();
}
static Future startListening() async {
await _speech.listen(
onResult: resultListener,
listenFor: Duration(minutes: 1),
cancelOnError: true,
partialResults: false);
return lastWords;
}
static void stopListening() {
_speech.stop();
}
static resultListener(SpeechRecognitionResult result) {
lastWords = "${result.recognizedWords}";
//print(lastWords);
if (lastWords != '') {
//this is what I want to pass to the Text variable on the Main Widget
print(lastWords);
}
}
}
我认为我只需要将 startListening
方法分配给主小部件中的 Text 变量
onLongPress: () {
setState(() async {
text = await Speech.startListening();
});
鉴于 athe
方法正在返回所需的字符串
static Future startListening() async {
await _speech.listen(
onResult: resultListener,
listenFor: Duration(minutes: 1),
cancelOnError: true,
partialResults: false);
return lastWords;
}
但该方法导致出现红屏错误:
════════ Exception caught by gesture
═══════════════════════════════════════════ The following assertion
was thrown while handling a gesture: setState() callback argument
returned a Future.
The setState() method on _HomePageState#0ff8a was called with a
closure or method that returned a Future. Maybe it is marked as
"async".
Instead of performing asynchronous work inside a call to setState(),
first execute the work (without updating the widget state), and then
synchronously update the state inside a call to setState().
When the exception was thrown, this was the stack
#0 State.setState. package:flutter/…/widgets/framework.dart:1270
#1 State.setState package:flutter/…/widgets/framework.dart:1286
#2 _HomePageState.build. lib\page\home_page.dart:75
#3 GestureRecognizer.invokeCallback package:flutter/…/gestures/recognizer.dart:182
#4 LongPressGestureRecognizer._checkLongPressStart package:flutter/…/gestures/long_press.dart:423 ... Handler:
"onLongPress" Recognizer: LongPressGestureRecognizer#ac97b
debugOwner: GestureDetector
state: possible ════════════════════════════════════════════════════════════════════════════════
I/flutter (11164): listening
════════ Exception caught by widgets library
═══════════════════════════════════ The method 'toLowerCase' was
called on null. Receiver: null Tried calling: toLowerCase() The
relevant error-causing widget was SubstringHighlight
lib\page\home_page.dart:45
════════════════════════════════════════════════════════════════════════════════
,所以我想我可以使用 resultListener
,只有在有 voicerecognition
结果时才会调用它,将 lastwords
字符串发送到主要的Widget,但是我还没有想出如何。
P.D。
让主要 Widget 上的所有方法都有效,但我试图实现干净的体系结构,所以我想将该逻辑与 Ui 小部件分开。
谢谢。
//--------更新----------------
我不断研究并找到了一种使用流使其工作的方法,
我在Speech中创建了一个stream controller class,然后在result listener中添加了一个sink。
static resultListener(SpeechRecognitionResult result) {
lastWords = "${result.recognizedWords}";
//print(lastWords);
if (lastWords != '') {
streamController.sink.add(lastWords);
}
在主 Widget 中,我在 OnLongPress 函数中实现了流监听。
onLongPress: () {
Speech.startListening();
Speech.streamController.stream.listen((data) {
print("recibido: $data");
setState(() {
text = data;
});
});
到目前为止它运行良好,但我不知道这是否是处理此数据传输的最佳方式。如果有人知道更好的方法,我将不胜感激,如果这是一个很好的方法,那将是这个问题的答案。
// .-......第二次更新--------------------
这种方法似乎可行,但我意识到一段时间后我开始多次获取相同的数据
I/flutter (20460): listening I/flutter (20460): notListening I/flutter
(20460): recibido: 123 I/chatty (20460):
uid=10166(com.example.speech_to_text_example) 1.ui identical 1 line 2
I/flutter (20460): recibido: 123 I/chatty (20460):
uid=10166(com.example.speech_to_text_example) 1.ui identical 1 line
I/flutter (20460): recibido: 123
对于这个不重要的特定示例,但如果我想将该字符串用于诸如使用 web 服务之类的事情,那是一个问题,我只想在每次开始记录时获取它一次。
谢谢。
我认为您需要在 lastwords 发生变化时通知家长 class 如果您想同时显示它。将您的语音识别 class 移动到小部件可以解决问题。在顶部创建一个变量而不是 lastwords 并显示在文本小部件中。
void startListening() async {
await _speech.listen(
onResult: (val) =>
setState(() {
_lastwords=val.recognizedWords;
});
cancelOnError: true,
partialResults: false);
}
我的方法是有一个额外的变量来存储未来,然后再将它分配给 setstate
中的文本
onLongPress: () async {
var txt = await Speech.startListening();
setState(() {
text = txt;
});
}
我以前的解决方案的问题是我使用 StreamController 作为广播,每次我使用麦克风按钮时听众都会创建一个实例,从而导致冗余数据。为了解决这种情况,我在 Speech class 中创建了一个流控制器,然后在结果监听器中添加了一个接收器。
StreamController<String> streamController = StreamController();
static resultListener(SpeechRecognitionResult result) {
lastWords = "${result.recognizedWords}";
//print(lastWords);
if (lastWords != '') {
streamController.sink.add(lastWords);
}
在主要的 Widget 中,我在 initState 中调用了 streamController 侦听器,因此侦听器仅实例化一次,这防止了冗余数据和 “Stream has already been listened to” 错误.
@override
void initState() {
super.initState();
initSpeechState();
speech.streamController.stream.listen((event) {
setState(() {
text = event;
print(text);
});
});
}
我正在使用 Speech_to_text 包 将语音识别结果存储在一个字符串变量中,我以后可以将其用于不同的目的,到目前为止,我只是想要在屏幕上显示字符串。我想实现类似于 Whatsaap 录音的功能,所以我有 GestureDetector
,onLongPress
开始录音,onLongPressUp
停止录音。
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
//Variable to show on screen
String text = 'Press the button and start speaking';
bool isListening = false;
final SpeechToText speech = SpeechToText();
String lastError = "";
String lastStatus = "";
String _currentLocaleId = "";
@override
void initState() {
super.initState();
initSpeechState();
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(MyApp.title),
centerTitle: true,
),
body: SingleChildScrollView(
reverse: true,
padding: const EdgeInsets.all(30).copyWith(bottom: 150),
child: SubstringHighlight(
text: text,
terms: Command.all,
textStyle: TextStyle(
fontSize: 32.0,
color: Colors.black,
fontWeight: FontWeight.w400,
),
textStyleHighlight: TextStyle(
fontSize: 32.0,
color: Colors.red,
fontWeight: FontWeight.w400,
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: AvatarGlow(
animate: isListening,
endRadius: 75,
glowColor: Theme.of(context).primaryColor,
child: GestureDetector(
child: FloatingActionButton(
child: Icon(isListening ? Icons.mic : Icons.mic_none, size: 36),
/*onPressed: () {
toggleRecording();
print(text);
}*/
onPressed: () {},
),
onLongPress: () {
setState(() async {
text = await Speech.startListening();
});
// print(text);
},
onLongPressUp: Speech.stopListening,
//sendMessage(),
),
),
);
Future<void> initSpeechState() async {
bool hasSpeech = await speech.initialize(
onError: errorListener, onStatus: statusListener, debugLogging: false);
}
//Speech to text methods
Future<void> errorListener(SpeechRecognitionError error) async {
print("Received error status: $error, listening: ${speech.isListening}");
if (mounted) {
setState(() {
lastError = "${error.errorMsg} - ${error.permanent}";
print(lastError);
});
}
}
void statusListener(String status) {
setState(() {
print(status);
lastStatus = "$status";
});
}
}
OnLongPress
和 OnLongPressUp
分别调用方法 startListening
和 stopListening
,它们在不同的 class.
class Speech {
static final _speech = SpeechToText();
static String lastWords;
void cancelListening() {
_speech.cancel();
}
static Future startListening() async {
await _speech.listen(
onResult: resultListener,
listenFor: Duration(minutes: 1),
cancelOnError: true,
partialResults: false);
return lastWords;
}
static void stopListening() {
_speech.stop();
}
static resultListener(SpeechRecognitionResult result) {
lastWords = "${result.recognizedWords}";
//print(lastWords);
if (lastWords != '') {
//this is what I want to pass to the Text variable on the Main Widget
print(lastWords);
}
}
}
我认为我只需要将 startListening
方法分配给主小部件中的 Text 变量
onLongPress: () {
setState(() async {
text = await Speech.startListening();
});
鉴于 athe
方法正在返回所需的字符串
static Future startListening() async {
await _speech.listen(
onResult: resultListener,
listenFor: Duration(minutes: 1),
cancelOnError: true,
partialResults: false);
return lastWords;
}
但该方法导致出现红屏错误:
════════ Exception caught by gesture ═══════════════════════════════════════════ The following assertion was thrown while handling a gesture: setState() callback argument returned a Future.
The setState() method on _HomePageState#0ff8a was called with a closure or method that returned a Future. Maybe it is marked as "async".
Instead of performing asynchronous work inside a call to setState(), first execute the work (without updating the widget state), and then synchronously update the state inside a call to setState().
When the exception was thrown, this was the stack #0 State.setState. package:flutter/…/widgets/framework.dart:1270 #1 State.setState package:flutter/…/widgets/framework.dart:1286 #2 _HomePageState.build. lib\page\home_page.dart:75 #3 GestureRecognizer.invokeCallback package:flutter/…/gestures/recognizer.dart:182 #4 LongPressGestureRecognizer._checkLongPressStart package:flutter/…/gestures/long_press.dart:423 ... Handler: "onLongPress" Recognizer: LongPressGestureRecognizer#ac97b debugOwner: GestureDetector state: possible ════════════════════════════════════════════════════════════════════════════════ I/flutter (11164): listening
════════ Exception caught by widgets library ═══════════════════════════════════ The method 'toLowerCase' was called on null. Receiver: null Tried calling: toLowerCase() The relevant error-causing widget was SubstringHighlight lib\page\home_page.dart:45 ════════════════════════════════════════════════════════════════════════════════
,所以我想我可以使用 resultListener
,只有在有 voicerecognition
结果时才会调用它,将 lastwords
字符串发送到主要的Widget,但是我还没有想出如何。
P.D。 让主要 Widget 上的所有方法都有效,但我试图实现干净的体系结构,所以我想将该逻辑与 Ui 小部件分开。
谢谢。
//--------更新----------------
我不断研究并找到了一种使用流使其工作的方法,
我在Speech中创建了一个stream controller class,然后在result listener中添加了一个sink。
static resultListener(SpeechRecognitionResult result) {
lastWords = "${result.recognizedWords}";
//print(lastWords);
if (lastWords != '') {
streamController.sink.add(lastWords);
}
在主 Widget 中,我在 OnLongPress 函数中实现了流监听。
onLongPress: () {
Speech.startListening();
Speech.streamController.stream.listen((data) {
print("recibido: $data");
setState(() {
text = data;
});
});
到目前为止它运行良好,但我不知道这是否是处理此数据传输的最佳方式。如果有人知道更好的方法,我将不胜感激,如果这是一个很好的方法,那将是这个问题的答案。
// .-......第二次更新--------------------
这种方法似乎可行,但我意识到一段时间后我开始多次获取相同的数据
I/flutter (20460): listening I/flutter (20460): notListening I/flutter (20460): recibido: 123 I/chatty (20460): uid=10166(com.example.speech_to_text_example) 1.ui identical 1 line 2 I/flutter (20460): recibido: 123 I/chatty (20460): uid=10166(com.example.speech_to_text_example) 1.ui identical 1 line I/flutter (20460): recibido: 123
对于这个不重要的特定示例,但如果我想将该字符串用于诸如使用 web 服务之类的事情,那是一个问题,我只想在每次开始记录时获取它一次。
谢谢。
我认为您需要在 lastwords 发生变化时通知家长 class 如果您想同时显示它。将您的语音识别 class 移动到小部件可以解决问题。在顶部创建一个变量而不是 lastwords 并显示在文本小部件中。
void startListening() async {
await _speech.listen(
onResult: (val) =>
setState(() {
_lastwords=val.recognizedWords;
});
cancelOnError: true,
partialResults: false);
}
我的方法是有一个额外的变量来存储未来,然后再将它分配给 setstate
中的文本onLongPress: () async {
var txt = await Speech.startListening();
setState(() {
text = txt;
});
}
我以前的解决方案的问题是我使用 StreamController 作为广播,每次我使用麦克风按钮时听众都会创建一个实例,从而导致冗余数据。为了解决这种情况,我在 Speech class 中创建了一个流控制器,然后在结果监听器中添加了一个接收器。
StreamController<String> streamController = StreamController();
static resultListener(SpeechRecognitionResult result) {
lastWords = "${result.recognizedWords}";
//print(lastWords);
if (lastWords != '') {
streamController.sink.add(lastWords);
}
在主要的 Widget 中,我在 initState 中调用了 streamController 侦听器,因此侦听器仅实例化一次,这防止了冗余数据和 “Stream has already been listened to” 错误.
@override
void initState() {
super.initState();
initSpeechState();
speech.streamController.stream.listen((event) {
setState(() {
text = event;
print(text);
});
});
}