Flutter 将数据从 class 传递到小部件变量的最佳方式

Flutter Best way to Pass data from a class to a widget variable

我正在使用 Speech_to_text 包 将语音识别结果存储在一个字符串变量中,我以后可以将其用于不同的目的,到目前为止,我只是想要在屏幕上显示字符串。我想实现类似于 Whatsaap 录音的功能,所以我有 GestureDetectoronLongPress 开始录音,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";
    });
  }
}

OnLongPressOnLongPressUp 分别调用方法 startListeningstopListening,它们在不同的 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);
      });
 
    });
  }