一尘不染

Flutter: Stream has already been listened to

flutter

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>>?


阅读 5798

收藏
2020-08-13

共1个答案

一尘不染

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.

2020-08-13