Reputation: 1208
I have a simple stream that returns a countdown when an operation is performed.
static Stream<double> counterStream = (() async* {
double i = 0.1;
double z = 0.0;
while (z < 0.99) {
await Future.delayed(Duration(seconds: 1));
yield z = z+i;
}
})();
I do not start it immediately when the widget is initialized, but by taping on the button that is inside StreamBuilder
onPressed: () {
setState(() {
_result = DatabaseAccess.counterStream;
});},
First time Stream run correctly, but the second time does not start, and if I close the screen and return to it again, start the stream - I get an error
Bad state: Stream has already been listened to.
I don’t know how to reload a stream that has already completed its execution and, in theory, has the status ConnectionState.done
by clicking on the button.
Full class code
class UpdatePage extends StatefulWidget {
UpdatePage({Key key, this.title}) : super(key: key);
static const String routeName = "/UpdatePage";
final String title;
@override
_UpdatePageState createState() => new _UpdatePageState();
}
class _UpdatePageState extends State<UpdatePage> {
Stream<double> _result;
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(""),
),
body: SafeArea(
child: StreamBuilder<double>(
stream: _result,
builder: (BuildContext context, AsyncSnapshot<double> snapshot) {
List<Widget> children;
if (snapshot.hasError) {
children = <Widget>[
Center(
child: Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
)
];
} else {
switch (snapshot.connectionState) {
case ConnectionState.none:
children = <Widget>[
Center(child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(child: Text("Обновление данных", style: new TextStyle( fontSize: 18.0))),
)),
Center(
child: Container(padding: EdgeInsets.all(20.0),child: RaisedButton.icon(
textColor: Colors.white,
icon: FaIcon(FontAwesomeIcons.retweet, size: 18,),
color: Color(0xFF6200EE), label: Text("Обновить"),
onPressed: () {
setState(() {
_result = DatabaseAccess.counterStream;
});
},
),),
),
];
break;
case ConnectionState.waiting:
children = <Widget>[
Center(
child: new Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
),
),
new CircularPercentIndicator(
radius: MediaQuery.of(context).size.width/2,
lineWidth: 4.0,
center: new Text('', style: new TextStyle( fontSize: 20.0)),
),
];
break;
case ConnectionState.active:
children = <Widget>[
Center(
child: new Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
),
),
new CircularPercentIndicator(
radius: MediaQuery.of(context).size.width/2,
lineWidth: 4.0,
percent: snapshot.data,
center: new Text('${snapshot.data.toStringAsFixed(2)}', style: new TextStyle( fontSize: 20.0)),
progressColor: Colors.orange,
),
];
break;
case ConnectionState.done:
children = <Widget>[
new CircularPercentIndicator(
radius: MediaQuery.of(context).size.width/2,
lineWidth: 4.0,
center: new Icon(
Icons.done,
size: MediaQuery.of(context).size.width/4,
color: Colors.green,
),
backgroundColor: Colors.grey,
progressColor: Colors.orange,
)
,
Center(child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(child: Text("Обновление успешно завершено", style: new TextStyle( fontSize: 18.0))),
)),
Center(
child: Container(padding: EdgeInsets.all(20.0),child: RaisedButton.icon(
textColor: Colors.white,
icon: FaIcon(FontAwesomeIcons.retweet, size: 18,),
color: Color(0xFF6200EE), label: Text("Обновить"),
onPressed: () {
setState(() {
_result = DatabaseAccess.counterStream;
});
},
),),
),
];
break;
}
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
);
},
)
));
;
}
}
Upvotes: 3
Views: 4140
Reputation: 11984
Since counterStream
is static, it is initialized once during the lifetime of the app. During its initialization, the async*
function is called once.
So, you create only one stream during the lifetime of the application. counterStream
is a reference to that stream.
The second time you press the button, you set the _result
to what it already was, the reference to the stream that I mentioned above. Therefore, StreamBuilder does not change anything. As far as it is concerned, it's a rebuild without change.
When you close the screen and come back, your new StreamBuilder
is trying to treat the existing, already listened to stream as a new stream, hence the error.
The solution
I think you are trying to restart a countdown every time the button is pressed. Editing the code to be like this may solve it:
static Stream<double> Function() counterStream = () async* {
double i = 0.1;
double z = 0.0;
while (z < 0.99) {
await Future.delayed(Duration(seconds: 1));
yield z = z+i;
}
};
onPressed: () {
setState(() {
_result = DatabaseAccess.counterStream();
});},
Upvotes: 1