Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter: Stream has already been listened to [duplicate]

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

like image 872
Jonas Avatar asked Dec 18 '18 22:12

Jonas


People also ask

What is StreamController flutter?

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.

How do you use StreamSubscription in flutter?

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.

How do I use StreamBuilder in flutter?

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 .


1 Answers

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.

like image 148
awaik Avatar answered Nov 04 '22 02:11

awaik