Creating Fresh State Objects When Multiple Instances of Stateful Widget Exist In the Tree

1,040 views
Skip to first unread message

bre...@tamu.edu

unread,
Jul 18, 2018, 2:33:10 PM7/18/18
to Flutter Dev
I have an issue where I need a separation of state of multiple instances of the same StatefulWidget in the tree. According to the Flutter documentation:

"The framework calls createState whenever it inflates a StatefulWidget, which means that multiple State objects might be associated with the same StatefulWidget if that widget has been inserted into the tree in multiple places."

I can certainly understand cases in which this is a useful feature, but due to this behavior, there doesn't seem to be true encapsulation of a StatefulWidget along with it's associated State class when multiple instances of the same StatefulWidget exist in the tree. Because of this, I'm having an issue where only the last instance of the StatefulWidget is properly adhering to state change, and none of the previous instances do. Perhaps there's something that I'm overlooking, but there doesn't seem to be a way to do this. Is this correct? If not, how do I keep the State objects along with their corresponding StatefulWidget objects fully encapsulated (and thus not interfering with other instances of the same StatefulWidget class)? I've tried adding unique local keys to each instance in an attempt to circumvent this behavior (and have the framework consider them as indeed separate instead of identical instances of the same StatefulWidget), but unfortunately this didn't help. I appreciate any suggestions (or perhaps corrections to my understanding of how this can be achieved). Thanks in advance!

Kris Giesing

unread,
Jul 19, 2018, 6:21:37 PM7/19/18
to bre...@tamu.edu, Flutter Dev
Do you happen to have a short code sample that illustrates the problem? That might make it easier to discuss the problem and possible options.

--
You received this message because you are subscribed to the Google Groups "Flutter Dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to flutter-dev...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

bre...@tamu.edu

unread,
Jul 20, 2018, 8:27:45 AM7/20/18
to Flutter Dev
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!

bre...@tamu.edu

unread,
Jul 20, 2018, 8:31:11 AM7/20/18
to Flutter Dev
I'm attempting to re-add the sample instantiation code...not sure why most of it was eaten on the last post...

import 'package:flutter/material.dart';

                title
: new Text('Widget Example'<span style="color

Kris Giesing

unread,
Jul 20, 2018, 4:17:16 PM7/20/18
to bre...@tamu.edu, Flutter Dev
It's possible that the TTS plugin is not intended to be instantiated more than once. That could cause the callbacks registered on multiple instances to be sent to incorrect clients.

Can you rework your example to avoid the TTS plugin and see if you still see incorrect behavior?

--

bre...@tamu.edu

unread,
Jul 23, 2018, 9:10:25 AM7/23/18
to Flutter Dev
Good call, Kris. I swapped it out with another StatefulWidget that I've written in my app, and have it generate a random int in it's constructor and attach this int to the key assigned to each instance. I'm getting exactly what I should be from this (each instance is properly encapsulated), so it does indeed seem to be pointing to the TTS plugin I'm using, just as you've suggested. Thanks for the excellent idea!

Kris Giesing

unread,
Jul 23, 2018, 3:54:15 PM7/23/18
to bre...@tamu.edu, Flutter Dev
Glad that helped :)

It should be possible for a plugin to work with multiple clients. So if you file a bug against the TTS plugin author they may be able to get the problem sorted.

Cheers,

- Kris
Reply all
Reply to author
Forward
0 new messages