Flutter : Bad state: Stream has already been listened to
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: new Scaffold(
appBar: TabBar(
tabs: [
Tab(child: Text("MY INFORMATION",style: TextStyle(color: Colors.black54),)),
Tab(child: Text("WEB CALENDER",style: TextStyle(color: Colors.black54),)),
],
),
body:PersonalInformationBlocProvider(
movieBloc: PersonalInformationBloc(),
child: TabBarView(
children: [
MyInformation(),
new SmallCalendarExample(),
],
),
),
),
);
}
}
class MyInformation extends StatelessWidget{
// TODO: implement build
var deviceSize;
//Column1
Widget profileColumn(PersonalInformation snapshot) => Container(
height: deviceSize.height * 0.24,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
decoration: BoxDecoration(
borderRadius:
new BorderRadius.all(new Radius.circular(50.0)),
border: new Border.all(
color: Colors.black,
width: 4.0,
),
),
child: CircleAvatar(
backgroundImage: NetworkImage(
"http://www.binaythapa.com.np/img/me.jpg"),
foregroundColor: Colors.white,
backgroundColor: Colors.white,
radius: 40.0,
),
),
ProfileTile(
title: snapshot.firstName,
subtitle: "Developer",
),
SizedBox(
height: 10.0,
),
],
)
],
),
);
Widget bodyData(PersonalInformation snapshot) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
profileColumn(snapshot)
],
),
);
}
@override
Widget build(BuildContext context) {
final personalInformationBloc = PersonalInformationBlocProvider.of(context);
deviceSize = MediaQuery.of(context).size;
return StreamBuilder(
stream: personalInformationBloc.results,
builder: (context,snapshot){
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator(),
);
return bodyData(snapshot.data);
}
);
}
}
I am using Bloc Pattern for retrieving data from Rest API (just called the whole object from JSON and parsed user name only). The Page consists of two tabs MyInformation and SmallCalendar. When the app runs the data are fetched correctly and everything is good. When I go to tab two and return to tab one then the whole screens in tab one goes to red showing error:
Bad state: Stream has already been listened to.
You should use the following.
StreamController<...> _controller = StreamController<...>.broadcast();
The most common form of Stream
can be listened only once at a time. If you try to add multiple listeners, it will throw
Bad state: Stream has already been listened to
To prevent this error, expose a broadcast Stream
. You can convert your stream to a broadcast using myStream.asBroadcastStream
This needs to be done inside your class that expose Stream
. Not as parameter of StreamBuilder
. Since asBroadcastStream
internally listen to the original stream to generate the broadcast one, this imply you can't call this method twice on the same stream.
You could use broadcast
, which allows to listen stream more than once, but it also prevents from listening past events:
Broadcast streams do not buffer events when there is no listener.
A better option is to use BehaviorSubject
from rxdart
package class as StreamController
. BehaviorSubject
is:
A special StreamController that captures the latest item that has been added to the controller, and emits that as the first item to any new listener.
The usage is as simple as:
StreamController<...> _controller = BehaviorSubject();
In my case, I was getting this error because the same line of code myStream.listen()
was being called twice in the same widget on the same stream. Apparently this is not allowed!
UPDATE: If you intend to subscribe to the same stream more than once, you should use a behavior subject instead:
// 1- Create a behavior subject
final _myController = BehaviorSubject<String>();
// 2- To emit/broadcast new events, we will use Sink of the behavior subject.
Sink<String> get mySteamInputSink => _myController.sink;
// 3- To listen/subscribe to those emitted events, we will use Stream (observable) of the behavior subject.
Stream<String> get myStream => _myController.stream;
// 4- Firstly, Listen/subscribe to stream events.
myStream.listen((latestEvent) {
// use latestEvent data here.
});
// 5- Emit new events by adding them to the BehaviorSubject's Sink.
myStreamInputSink.add('new event');
That's it!
However, there is one final important step.
6- We must unsubscribe from all stream listeners before a widget is destroyed.
Why? (You might ask)
Because if a widget subscribes to a stream, and when this widget is destroyed, the destroyed widget stream subscription will remain in app memory causing memory leaks and unpredictable behavior.:
_flush() {
_myController.close();
_myController = StreamController<String>();
}
############################### ###############################
Old Answer:
What fixed it for me is to both create a my stream controller as a broadcast stream controller:
var myStreamController = StreamController<bool>.broadcast();
AND
use stream as a broadcast stream:
myStreamController.stream.asBroadcastStream().listen(onData);