Search code examples
flutterdartspeech-to-text

Can't pass updated state of variable dynamically from screen/page 1 to screen/page 2


I created an instantiated an object (speech) in screen 1 and then called the initialization method for that object, where I set the error handling (var lastError gets the error output). Then I passed to screen 2 the object as an argument using Navigator.pushNamed where I call some other speech methods.

The problem I'm facing is that when an error arise while being in screen 2, I can't get the error output since the initialize and error catching was defined in screen 1.

What I have tried so far:

  1. Passing the lastError via GlobalKey, but it only passes the state at the time the widget of screen 2 is build, but not dinamically as it does in screen 1.
  2. Adding the errorHandling method in screen 2 but always the object calls the errorHandling method in screen 1.
  3. Adding setState for the key.currentState.lastError, but anyway it is not dynamically updated.

I can’t pass the initialize calling to page 2 since I need to confirm the object is working in screen 1 prior to pass to screen 2 to record voice.

Any advice how to get the error updated dynamically in screen 2?

class PassedSpeechComponent {
  stt.SpeechToText speech;
  String lastError;
  PassedSpeechComponent(this.speech, this.lastError, this.lastStatus);
}

final key = new GlobalKey<_MainPgState>();

void main() {
  runApp(MaterialApp(
    title: 'Routes',
    initialRoute: '/',
    routes: {
      '/': (context) => MainPg(key: key),
      '/speech': (context) => SpeechCapturePg(),
    },
  ));
}

class MainPg extends StatefulWidget {
  MainPg({ Key key }) : super(key: key);
  @override
  _MainPgState createState() => _MainPgState();
}

// ***** SCREEN 1 ******
class _MainPgState extends State<MainPg> {
  var lastError = "";
  var lastStatus = "";
  bool _available = false;
  stt.SpeechToText speech = stt.SpeechToText();

  // speech error listener of 1st screen
  void errorListener(SpeechRecognitionError error) { 
     setState(() {
      lastError = "${error.errorMsg} - ${error.permanent}";
      print('@@@ Called error listener of 1st screen @@@');
    });
    speech.stop();
  }

  Future<void> initSpeechState() async {
    bool available = await speech.initialize(
       onError: errorListener);
      setState(() {
      _available = available;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!_available) {
      initSpeechState();
    } 
    return Scaffold(
      appBar: AppBar(
        title: Text("--"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Initializing:$_available',
                style: TextStyle(
                  color: _available ? Colors.green : Colors.black,
                  fontSize: 15,
                )),
            Text('isListening:${speech.isListening}',
                style: TextStyle(
                  color: speech.isListening ? Colors.green : Colors.black,
                  fontSize: 15,
                )),
                Text('Error:' + lastError, // LastError alert on first screen working as intended.
                    style: TextStyle(
                      color:
                        lastError != "" ? Colors.red : Colors.black,
                        fontSize: 15,
                )),
            Align(        
              child: Stack(
                  alignment: AlignmentDirectional.bottomCenter,
                  children: <Widget>[
                    SizedBox(
                      width: 110.0,
                      height: 110.0,
                      child: Visibility(
                        visible: !speech.isListening,
                        child: FloatingActionButton(
                          onPressed: () {
                            if (_available) {
                              Navigator.pushNamed(
                                context,
                                '/speech',
                                arguments: {speech, errorListener, statusListener},
                              );
                            } else {
                              initSpeechState();
                            }
                          },
                          tooltip: 'A single tap is enough',
                          child: Icon(Icons.mic),
                        ),
                      ),
                    ),
                  ]),
            ),
          ],
        ),
      ),
      );
  }
}

// ******** SECOND SCREEN ********
class SpeechCapturePg extends StatefulWidget {
  final String title;

const SpeechCapturePg({Key key, this.title}) : super(key: key);
  @override
  _SpeechCapturePgState createState() => _SpeechCapturePgState();
}

class _SpeechCapturePgState extends State<SpeechCapturePg>
    with TickerProviderStateMixin {
  String lastWords = "";
  String lastError = "";
  
  @override
  void initState() {
    super.initState();
   
    WidgetsBinding.instance.addPostFrameCallback((_) {
      startListening(ModalRoute.of(context).settings.arguments);
    });

      setState(()
      {
        // Here I try to update dinamically lastError anytime 
        // last error of first screen is updated, but is not working... =(
        lastError = key.currentState.lastError;
        print("Last Error" + key.currentState.lastError);
      });
  }

  @override
  dispose() {
    super.dispose();
  }

  // This method is never called in this screen/page, just the one created in screen 1 
  void errorListener(SpeechRecognitionError error) {
    setState(() {
      print('Called in 2nd screen:'$lastError);
    });
  }

  
  void startListening(speech) {
    lastWords = "";
    lastError = "";    
    // 
    speech.first.listen(
      onResult: resultListener,
      cancelOnError: true,
    );
  }

  void resultListener(SpeechRecognitionResult result) {
    setState(() {
      lastWords = "${result.recognizedWords}";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.redAccent,
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Expanded(
                    flex: 30,
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child:
                        Text('$lastWords')
                  )),
                  //***** HERE I NEED TO SHOW THE ERROR REPORTED BY OBJECT *****/                        
                  Text('Error - $lastError'),
                  // **** 2ND TRY TO SHOW ERROR REPORTED IN FIRST SCREEN
                  Text('Error' + key.currentState.lastError),                  
            ])));
  }
}


Solution

  • You are trying to share state between two views. For that, you need a state management solution. Check out the Provider package. This is the approach recommended by the Flutter team.