You bet, Kris! Here's the StatefulWidget class along with it's associated State class. It creates and controls a Play and Stop IconButton for text-to-speech playback (it uses a library that provides Flutter support for Google's excellent text-to-speech service):
import "package:flutter/material.dart";
import 'package:flutter_tts/flutter_tts.dart';
import 'dart:async';
class TextToSpeechWidget extends StatefulWidget {
final String textualData;
TextToSpeechWidget({Key key, @required this.textualData}) : super(key: key);
_TextToSpeechWidgetState createState() => new _TextToSpeechWidgetState();
}
class _TextToSpeechWidgetState extends State<TextToSpeechWidget> {
String _newVoiceText;
FlutterTts _flutterTts = new FlutterTts();
String language = "en-US";
bool _isStopped = true;
bool _isPlaying = false;
@override
initState() {
super.initState();
_initTts();
}
_initTts() async {
_flutterTts.setStartHandler(() {
print("runtimeType is: ${widget.runtimeType}");
setState(() {
_isPlaying = true;
_isStopped = false;
});
});
_flutterTts.setCompletionHandler(() {
setState(() {
_isStopped = true;
_isPlaying = false;
});
print("setCompletionHandler called");
});
_flutterTts.setErrorHandler((msg) {
setState(() {
_isStopped = true;
_isPlaying = false;
});
});
}
Future _speak() async {
_flutterTts.setLanguage(language);
var result = await _flutterTts.speak(_newVoiceText);
if(result == 1) {
setState(() {
_isPlaying = true;
_isStopped = false;
});
}
}
Future _stop() async {
var result = await _flutterTts.stop();
if(result == 1) {
setState(() {
_isStopped = true;
_isPlaying = false;
});
}
}
@override
void dispose() {
super.dispose();
_flutterTts.stop();
}
Widget build(BuildContext context) {
return new Row(
key: widget.key,
children: <Widget>[
new Container(
margin: new EdgeInsets.all(0.0),
padding: new EdgeInsets.all(0.0),
child: new IconButton(
tooltip: "Tap here for Text-To-Speech",
icon: new Icon(Icons.play_arrow),
onPressed: () {
_newVoiceText = widget.textualData;
_speak();
},
color: Colors.green,
splashColor: Colors.greenAccent,
),
),
new Container(
margin: new EdgeInsets.all(0.0),
padding: new EdgeInsets.all(0.0),
child: new IconButton(
icon: new Icon(Icons.stop),
onPressed: _isStopped ? null : () => _stop(),
color: Colors.red,
splashColor: Colors.redAccent,
),
),
],
);
}
}
A simplified example of my instantiations of the stateful TextToSpeechWidget class:
import 'text_to_speech_widget.dart';
class WidgetExample extends StatelessWidget {
final String firstTextExample = "Example text 1 for text-to-speech playback";
final String secondTextExample = "Example text 2 for text-to-speech playback";
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Widget Example'),
),
body: new Column(
children: <Widget>[
// a much simpler tree structure from what I'm actually using
new Text("$firstTextExample"),
new TextToSpeechWidget(textualData: "$firstTextExample"),
new Text("$secondTextExample"),
new TextToSpeechWidget(textualData: "$secondTextExample"),
]
),
);
}
}
Each instantiation of the TextToSpeech widget exists to offer playback of the printed text above it (this is for older individuals who have difficulty reading smaller text). The corresponding Stop button is supposed to automatically deactivate when the playback completes (it turns grey when null is passed to it's onPressed listener). The problem, however, is that the State object of the last instantiation of the TextToSpeechWidget class overrides any previous instantiation. For clarification: If the user presses the Play button for the first instance, the proper text is spoken as it should be - the corresponding Stop button turns red indicating it can be used to stop the speech playback at any time...but this also occurs for the last instance's Stop button - it immediately turns red as well - and the Stop button in the last instance is the only widget that will automatically deactivate (turn grey) as it should, no matter which instance's Play button was pressed. If I only had a single instance on any given screen, this feature would work exactly as intended. The problem only arises when multiple instances occur on any screen (and I do need multiple on certain screens). Regardless of which instance's Play button is tapped, the very last instance's Stop button is the only one to properly observe state change.
I hope this isn't too confusing, and I will happily clarify anything you need!