Flutter:流已被收听 [英] Flutter: Stream has already been listened to

查看:87
本文介绍了Flutter:流已被收听的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用BLoC从Firestore加载预设对象。这是我的Bloc模型:

  class StatisticsBloc扩展了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.fromDoc(快照);
_presets.add(预设);
});
_presetsController.sink.add(_presets);
}
}

然后我显示如下列表:

  class StatisticsPage扩展了StatelessWidget {

StatisticsPage(){
print('Created StatisticsPage');
}

@override
小部件构建(BuildContext上下文){
final StatisticsBloc statisticsBloc = BlocProvider.of< StatisticsBloc>(上下文);
final List< Preset> _ = [];

print(statisticsBloc.getPresets.isBroadcast);

返回脚手架(
appBar:AppBar(
标题:Text('Statistics'),
),
正文:StreamBuilder(
流:statisticsBloc.getPresets,
initialData:_,
构建器:(BuildContext上下文,AsyncSnapshot< List< Preset>>快照){
if(snapshot.hasData){
返回ListView(
子级:snapshot.data.map((预设预设){
print(preset.name);
返回新的ListTile(
标题:new Text(preset。名称),
字幕:new Text(preset.id),
);
})。toList(),
);
} else {
Text('No Data');
print('No Data');
}
}

);
}
}

问题是,我显示了<$ c标签栏中的$ c> StatisticsPage ,因此当我切换标签并返回到该标签时,构建时间会翻倍。第一次访问时它可以工作,但是当我切换选项卡并返回到它时,该小部件将重新生成,并且出现错误: Bad state:流已经被监听。 。我试图将 getPresets 流声明为BroadcastStream,正如您在 StatisitcsBloc 中看到的那样,但这是行不通的。 / p>

还有一个次要问题:是否有更好的方法将我从Firestore获得的 Stream< QuerySnapshot> 转换为 Stream< List< Presets>>

解决方案

很容易,请查看 BehaviorSubject类来自 RxDart库


BehaviorSubject在默认情况下是广播(又称热门)控制器,以实现Rx主题合同。这意味着可以多次收听主题流。


因此,只需更改行

  StreamController< List< Preset>> _presetsController = new StreamController(); 

  StreamController< List< Preset>> _presetsController = new BehaviorSubject(); 

并全部删除

  .asBroadcastStream()

就是这样!



在官方文档中,不建议使用asBroadcastStream()


创建流控制器的更危险的方法是通过asBroadcastStream()查看单个订阅控制器。调用asBroadcastStream基本上可以告诉单订阅流用户想要接管该流的生命周期管理。与cancelOnError订阅服务器结合使用,很容易导致单流订阅永远不会关闭,从而泄漏内存或资源。



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

解决方案

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.

这篇关于Flutter:流已被收听的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆