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:
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.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),
])));
}
}
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.