I’m using BLoC to load my Preset Objects from Firestore. This is my Bloc Model:
class StatisticsBloc extends BlocBase { List<Preset> _presets; StreamController<List<Preset>> _presetsController = new StreamController(); Stream<List<Preset>> get getPresets => _presetsController.stream.asBroadcastStream(); StatisticsBloc() { print('init Statistics Bloc'); _presets = []; Firestore.instance.collection('Presets').snapshots().asBroadcastStream().listen(_onPresetsLoaded); } @override void dispose() { print('Disposed Statistics Bloc'); _presetsController.close(); } void _onPresetsLoaded(QuerySnapshot data) { _presets = []; data.documents.forEach((DocumentSnapshot snap) { Preset preset = Preset.fromDoc(snap); _presets.add(preset); }); _presetsController.sink.add(_presets); } }
Then I display the List like this:
class StatisticsPage extends StatelessWidget { StatisticsPage() { print('Created StatisticsPage'); } @override Widget build(BuildContext context) { final StatisticsBloc statisticsBloc = BlocProvider.of<StatisticsBloc>(context); final List<Preset> _ = []; print(statisticsBloc.getPresets.isBroadcast); return Scaffold( appBar: AppBar( title: Text('Statistics'), ), body: StreamBuilder( stream: statisticsBloc.getPresets, initialData: _, builder: (BuildContext context, AsyncSnapshot<List<Preset>> snapshot) { if (snapshot.hasData) { return ListView( children: snapshot.data.map((Preset preset) { print(preset.name); return new ListTile( title: new Text(preset.name), subtitle: new Text(preset.id), ); }).toList(), ); } else { Text('No Data'); print('No Data'); } } ) ); } }
The problem is, I show the the StatisticsPage in a Tabbar, so it will be build muliple times when I switch tabs and go back to it. On the first visit it works but when I switch tabs and go back to it, the widget get rebuild and I get the error: Bad state: Stream has already been listened to.. I tried to declare the getPresets Stream as a BroadcastStream as you can see in StatisitcsBloc but that doesn’t work.
StatisticsPage
Bad state: Stream has already been listened to.
getPresets
StatisitcsBloc
Also as a secoundary question: Is there a better way to transform Stream<QuerySnapshot> that I get from Firestore to Stream<List<Presets>>?
Stream<QuerySnapshot>
Stream<List<Presets>>
It is easy, take a look to BehaviorSubject class from RxDart library.
BehaviorSubject is, by default, a broadcast (aka hot) controller, in order to fulfill the Rx Subject contract. This means the Subject’s stream can be listened to multiple times.
So, just change line
StreamController<List<Preset>> _presetsController = new StreamController();
to
StreamController<List<Preset>> _presetsController = new BehaviorSubject();
and delete all
.asBroadcastStream()
That’s it!
In official documentation it is not recommended to use asBroadcastStream()
A more dangerous way of creating a stream controller is to view a single- subscription controller through asBroadcastStream(). Invoking asBroadcastStream basically tells the single-subscription stream that the user wants to take over the lifetime management of the stream. In combination with cancelOnError subscribers, this can easily lead to single- stream subscriptions that are never closed and thus leak memory or resources.