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.
Also as a secoundary question: Is there a better way to transform Stream<QuerySnapshot>
that I get from Firestore to Stream<List<Presets>>
?
StreamController<T> class Null safety. A controller with the stream it controls. This controller allows sending data, error and done events on its stream. This class can be used to create a simple stream that others can listen on, and to push events to that stream.
A subscription on events from a Stream. When you listen on a Stream using Stream. listen, a StreamSubscription object is returned. The subscription provides events to the listener, and holds the callbacks used to handle the events.
To use StreamBuilder , you need to call the constructor below. Basically, you need to create a Stream and pass it as the stream argument. Then, you have to pass an AsyncWidgetBuilder which can be used to build the widget based on the snapshots of the Stream .
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With