Flutter:状态错误:流已被收听

时间:2018-07-18 08:08:04

标签: dart flutter


    class MyPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return DefaultTabController(
          length: 2,
          child: new Scaffold(
            appBar: TabBar(
              tabs: [
                Tab(child: Text("MY INFORMATION",style: TextStyle(color: Colors.black54),)),
                Tab(child: Text("WEB CALENDER",style: TextStyle(color: Colors.black54),)),
              ],
            ),
            body:PersonalInformationBlocProvider(
              movieBloc: PersonalInformationBloc(),
              child: TabBarView(
                children: [
                  MyInformation(),
                  new SmallCalendarExample(),
                ],
              ),
            ),
          ),
        );
      }
    }

    class MyInformation extends StatelessWidget{
      // TODO: implement build
      var deviceSize;

      //Column1
      Widget profileColumn(PersonalInformation snapshot) => Container(
        height: deviceSize.height * 0.24,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Container(
                  decoration: BoxDecoration(
                    borderRadius:
                    new BorderRadius.all(new Radius.circular(50.0)),
                    border: new Border.all(
                      color: Colors.black,
                      width: 4.0,
                    ),
                  ),
                  child: CircleAvatar(
                    backgroundImage: NetworkImage(
                        "http://www.binaythapa.com.np/img/me.jpg"),
                    foregroundColor: Colors.white,
                    backgroundColor: Colors.white,
                    radius: 40.0,
                  ),
                ),
                ProfileTile(
                  title: snapshot.firstName,
                  subtitle: "Developer",
                ),
                SizedBox(
                  height: 10.0,
                ),
              ],
            )
          ],
        ),
      );
      Widget bodyData(PersonalInformation snapshot) {
        return SingleChildScrollView(
            child: Column(
              children: <Widget>[
                profileColumn(snapshot)
              ],
            ),
        );
      }


      @override
      Widget build(BuildContext context) {
        final personalInformationBloc = PersonalInformationBlocProvider.of(context);

        deviceSize = MediaQuery.of(context).size;
        return StreamBuilder(
            stream: personalInformationBloc.results,
            builder: (context,snapshot){
              if (!snapshot.hasData)
                return Center(
                  child: CircularProgressIndicator(),
                );
              return bodyData(snapshot.data);
            }
        );
      }
    }


我正在使用Bloc模式从Rest API检索数据(仅从json调用整个对象,并且仅解析用户名)。该页面包含两个选项卡MyIformation和SmallCalendar。当应用程序运行时,将正确提取数据,一切正常。当我转到选项卡二并返回到选项卡一时,选项卡一的整个屏幕变为红色,显示错误: Bad state: Stream has already been listened to.

15 个答案:

答案 0 :(得分:19)

您应该使用以下内容。

StreamController<...> _controller = StreamController<...>.broadcast();

答案 1 :(得分:9)

Stream的最常见形式一次只能收听一次。如果您尝试添加多个侦听器,它将抛出

  

状态不良:流已经被收听

为防止此错误,请公开广播 Stream。您可以使用myStream.asBroadcastStream

将流转换为广播

这需要在暴露Stream的类中完成。不作为StreamBuilder的参数。由于asBroadcastStream在内部侦听原始流以生成广播流,因此这意味着您不能在同一流上两次调用此方法。

答案 2 :(得分:6)

您可以使用broadcast,它可以多次收听流,但是也可以防止过去的事件:

  

没有侦听器时,广播流不会缓冲事件。

一个更好的选择是将BehaviorSubject包类中的rxdart用作StreamControllerBehaviorSubject是:

  

一个特殊的StreamController,它捕获已添加到控制器中的最新项,并将其作为所有新侦听器的第一项发出。

用法很简单:

StreamController<...> _controller = BehaviorSubject();

答案 3 :(得分:2)

问题是由于未将控制器放置在bloc中。

  void dispose() {
    monthChangedController.close();
    dayPressedController.close();
    resultController.close();
  }

答案 4 :(得分:1)

总结一下:

主要区别是broadcast() 创建一个Stream,可以监听多个来源,但是至少要监听一个来源才能开始发射物品。

  

在订阅者开始侦听它之前,流应该是惰性的(使用[onListen]回调开始产生事件)。

asBroadcastStream将现有的Stream变成了一个可多听的声音,但由于它在幕后调用了onListen(),因此无需发出声音就可以开始发出声音。

答案 5 :(得分:1)

对于进行Future.asStream()时遇到此问题的人,您需要Future.asStream().shareReplay(maxSize: 1)使其成为广播/热门流。

答案 6 :(得分:1)

对于我来说,将流定义为全局变量是可行的

流信息流(位于...状态内的有状态小部件中,我在小部件外部定义了它,并且起作用了

(不确定最佳解决方案,但请尝试一下)

答案 7 :(得分:0)

当我将Observable.combineLatest2的结果用于StreamBuilder到抽屉中时,我遇到了同样的问题:

  

颤振:状态错误:流已经被收听。

对于我来说,最好的解决方案是将此合并的结果添加到新的BehaviorSubject中,并收听新的。

别忘了听旧的!!

class VisitsBlocc extends Object {
    Map<Visit, Location> visitAndLocation;

    VisitsBloc() {
        visitAndLocations.listen((data) {
            visitAndLocation = data;
        });
    }

    final _newOne = new BehaviorSubject<Map<Visit, Location>>();

    Stream<Map<Visit, Location>> get visitAndLocations => Observable.combineLatest2(_visits.stream, _locations.stream, (List<vis.Visit> visits, Map<int, Location> locations) {
        Map<vis.Visit, Location> result = {};

        visits.forEach((visit) {
            if (locations.containsKey(visit.skuLocationId)) {
                result[visit] = locations[visit.skuLocationId];
            }
        });

        if (result.isNotEmpty) {
            _newOne.add(result);
        }
    });
}

我没有使用.broadcast,因为它减慢了UI的速度。

答案 8 :(得分:0)

在我的情况下,出现此错误是因为在同一流的同一小部件​​中两次调用同一行代码myStream.listen()。显然这是不允许的!

对我来说固定的是都创建一个我的流控制器作为广播流控制器:

var myStreamController = StreamController<bool>.broadcast();

AND

使用流作为广播流:

myStreamController.stream.asBroadcastStream().listen(onData);

答案 9 :(得分:0)

StreamSplitter.split()中的

async可用于该用例

import 'package:async/async.dart';
...

main() {
  var process = Process.start(...);
  var stdout = StreamSplitter<List<int>>(process.stdout);
  readStdoutFoo(stdout.split());
  readStdoutBar(stdout.split());
}

readStdoutFoo(Stream<List<int>> stdout) {
  stdout.transform(utf8.decoder)...
}

readStdoutBar(Stream<List<int>> stdout) {
  stdout.transform(utf8.decoder)...
}

答案 10 :(得分:0)

在我的情况下,我是在Flutter Web上使用程序包连接。 评论所有的连通性呼叫可以解决该问题。

我现在仅在Android / iOS上使用连接功能。

因此,如果您正在为Web开发,则可能正在检查您的Packages,因为您使用的Web上有一些问题的软件包。

希望我可以为某人提供此信息。

答案 11 :(得分:0)

这是提供者的问题,我通过更改提供者初始化解决了

例如

locator.registerSingleton<LoginProvider>(LoginProvider());

 locator.registerFactory(() => TaskProvider());

定位器在哪里

GetIt locator = GetIt.instance;

答案 12 :(得分:0)

这可以帮助任何其他人。就我而言,我在每个选项卡中使用了两个 StreamBuilder。因此,当我滑动到选项卡上方并返回时。另一个流已经被监听,所以我得到了错误。

我所做的是从选项卡中删除 StreamBuilder 并将其放在顶部。每次有变化时我都会设置状态。我返回一个空的 Text('') 以避免显示任何内容。我希望这种方法

答案 13 :(得分:0)

对于其他情况。注意您是否以某种方式在无状态类中使用流。这是您收到上述错误的原因之一。 将无状态类转换为有状态并在流控制器上调用 init 和 dispose 方法:

 @override
 void initState() {
   super.initState();
   YourStreamController.init();
 }

 @override
 void dispose() {
   YourStreamController.dispose();
   super.dispose();
 }

答案 14 :(得分:0)

我认为并非所有答案都考虑了您不想要或根本无法使用广播流的情况。

通常情况下,您必须依赖接收过去的事件,因为侦听器的创建时间可能晚于它所侦听的流,因此接收此类信息很重要。

在 Flutter 中,经常发生的情况是监听流的小部件(“监听器”)被销毁并重新构建。如果您尝试将侦听器附加到与以前相同的流,您将收到此错误。

要克服这个问题,您必须手动管理您的流。我创建了 this gist 来演示如何做到这一点。您还可以在 this dartpad 上运行此代码以查看它的行为和使用它。我使用了简单的 Collision2D collision id 来引用特定的 String 实例,但也可能有更好的解决方案(可能是 symbols)。

来自要点的代码是:

StreamController