【问题标题】:Flutter DataStream not closing and re-building properly. [Bad state: Stream has already been listened to.]Flutter DataStream 未正确关闭和重新构建。 [不良状态:Stream 已经被收听。]
【发布时间】:2020-05-22 20:21:26
【问题描述】:

好的,所以我知道使用广播系统可以制造一个流来不止一次地收听一个流,但这并不是我在这里想要做的。

我也在编辑此问题,因为我收到的一个答案目前无法解决我的问题,因此我们将不胜感激。

由于某种原因,我的代码实际上并没有完全删除流,如果重新使用,它会尝试重新收听已经收听和关闭的同一个流,其中没有一个工作(显然)。我没有尝试再次收听相同的流,而是尝试创建一个新的流来收听。 (删除并清除原始第一个流中的所有信息)。

原帖继续如下:

我正在使用 DataStream 模板将数据流式传输到和/或从我的程序的各个部分流出,我不完全确定如何纠正这个问题。我确定这是一个愚蠢的新手错误,但我没有使用 DataStreams 来理解为什么会发生这种情况。

现在不要误会我的意思,通过我的程序的一个周期可以正常工作,完全没有问题。但是,一旦我通过程序完成了一个循环,如果我尝试第二次,我会收到错误:

错误状态:Stream 已被监听。

因此,我知道我的程序没有创建新流,而是尝试重新使用原始流,而且我不能 100% 确定如何停止此功能,(或者即使我应该)。 (老实说,我希望完成多个周期的次数几乎为零,但我想在这些错误成为问题之前解决它们。)

编辑:要遵循的最小可重复示例

文件 1 (main.dart)

import 'package:flutter/cupertino.dart';
import 'dart:async';
import './page2.dart';
import './stream.dart';

void main() => runApp(MyApp());

DataStream stream = DataStream();


class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      title: 'Splash Test',
      theme: CupertinoThemeData(
        primaryColor: Color.fromARGB(255, 0, 0, 255),
      ),
      home: MyHomePage(title: 'Splash Test Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool textBool = false;
  int counter = 0;

  void changeTest(context) async {
    int counter = 0;
    Timer.periodic(Duration (seconds: 2), (Timer t) {
      counter++;
      stream.dataSink.add(true);
      if (counter >= 3) {
        t.cancel();
        stream.dispose();
        Navigator.pop(context);
      } 
    },);
    Navigator.push(context, CupertinoPageRoute(builder: (context) => Page2(stream: stream)));
  }



  @override
  Widget build(BuildContext context) {

    return CupertinoPageScaffold(
      child: Center(
        child: CupertinoButton(
          child: Text('To Splash'),
          onPressed: () => changeTest(context),
        ),
      ), 
    );
  }
}

文件 2 (stream.dart)

import 'dart:async';

class DataStream {
  StreamController _streamController;

    StreamSink<bool> get dataSink =>
      _streamController.sink;

  Stream<bool> get dataStream =>
      _streamController.stream;

  DataStream() {
    _streamController = StreamController<bool>();
  }

  dispose() {
    _streamController?.close();
  }

}

文件 3 (page2.dart)

import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';

import './main.dart';
import './stream.dart';


class Page2 extends StatefulWidget {

  DataStream stream;
  Page2({this.stream});

  @override 
  State<StatefulWidget> createState() => new PageState();
}

class PageState extends State<Page2> {


bool textChanger = false;
bool firstText = true;

Text myText() {
  if (textChanger) {
    Text text1 = new Text('Text One', 
      style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
    return text1;
  } else {
    Text text1 = new Text('Text Two', 
      style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
    return text1;
  }
}

void changeText() {
  if (!firstText) {
    if (textChanger) {
      print('Change One');
      setState(() { 
        textChanger = false;      
      });
    } else {
      print('Change Two');
      setState(() {  
        textChanger = true;    
      });
    }  
  } else {
    firstText = false;
  }
}


  @override 
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Container(
        child: Center(
          child: myText()
        ) 
      ),);
  }

@override
  void initState() {
    super.initState();
    widget.stream.dataStream.listen((onData) {
      changeText();
    });
  }


}

实际上,在本例中,您可以单击文本,然后转到第二页,这将在被告知时正确更改文本,并在完成后返回原始页面。那将是我程序的一个“循环”。

你可以看到这个程序会立即处理流。

问题是,如果我再次点击该文本,它仍在尝试收听原始流,而不是创建一个全新的流并重新开始。

为什么?我该如何解决这个问题?

【问题讨论】:

  • 第二个代码块中stream的作用域是什么?从您拥有的 sn-p 来看,它似乎永远不会被重新实例化?
  • “我的程序周期”是什么意思?这可能与导航以及在下一个“周期”中如何推送/弹出 SplashPage 有关。您可以尝试向 SplashPage 添加密钥。
  • @emerssso 我觉得这可能是我遇到的问题之一,但我不确定如何解决它。我的意图是让流实例化,并在需要时收听,并在完成分配的任务后删除。如果需要,可以创建一个全新的。我不认为我正在这样做,但我不一定知道该怎么做。
  • @kuhnroyal “这个程序的循环”我的意思是这个程序被设计成做一件事,用户会通过一系列的提示来完成这个过程。一旦它完成了一次过程,我需要程序完全忘记最初发生的一切,重新开始。如果它试图重新使用原始信息,则会导致问题。
  • 如果流只在 SplashPage 中需要,那么就在那里实例化它,否则你肯定有多个订阅者。

标签: ios flutter data-stream


【解决方案1】:

StreamController 默认构造函数创建一个只允许单个侦听器的流。

StreamController({void onListen(), void onPause(), void onResume(), dynamic onCancel(), bool sync: false })
A controller with a stream that supports only one single subscriber. [...]

如果您想拥有多个侦听器,请使用 broadcast 命名构造函数。

factory StreamController.broadcast({void onListen(), void onCancel(), bool sync: false })
A controller where stream can be listened to more than once. [...]

如果您希望您的流只有一个订阅者,请记住在小部件的 dispose 方法中取消您的订阅。

DataStream stream;
StreamSubscription subscription;

@override
void initState() {
  super.initState();
  subsription = widget.stream.listen((onData) {
    changeText();
  });
}

@override
void dispose() {
  subscription?.cancel();
  super.dispose();
}

请记住,这不是基于流事件重建 UI 的正确方法。看看Stream Builder 类。

【讨论】:

  • 好的...感谢您提供可能的答案,但是如果我不想要多个听众该怎么办? (这是我的意图。也许我不能按照我想要的方式做,但我们会看到的)。我的意思是我想完全刷新我的程序,就像我从来没有开始流一样,所以它不应该有这个问题。
  • 好的,这就是重点。关键的事实是,每当您开始收听stream.listen((onData() {}); 流时,您都会得到一个名为StreamSubscription 的对象。您需要保留对它的引用,并 cancel 小部件的 dispose 方法中的订阅。
  • 我认为这可能是我的问题的一部分,因为我正在使用 dipose() 方法(并且我检查并被调用)但我不认为我正在创建一个新流,我不确定为什么它不创建一个新的流并收听那个流。
  • 您正在释放流很好,但是在释放小部件时您是否取消订阅?
【解决方案2】:

我要做的是将流移动到 StatefulWidget 并在“到 Splash”点击时重新创建它

在实际情况下,将其放入小部件树中的有状态小部件中,所有需要访问的小部件都可以找到它(在您的情况下甚至高于导航器)。

import 'package:flutter/cupertino.dart';
import 'dart:async';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      title: 'Splash Test',
      theme: CupertinoThemeData(
        primaryColor: Color.fromARGB(255, 0, 0, 255),
      ),
      home: MyHomePage(title: 'Splash Test Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool textBool = false;
  int counter = 0;

  DataStream stream = DataStream();

  void changeTest(context) async {
    setState(() {
      stream = DataStream();
    });
    int counter = 0;
    Timer.periodic(Duration (seconds: 2), (Timer t) {
      counter++;
      stream.dataSink.add(true);
      if (counter >= 3) {
        t.cancel();
        stream.dispose();
        Navigator.pop(context);
      }
    },);
    Navigator.push(context, CupertinoPageRoute(builder: (context) => Page2(stream: stream)));
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: Center(
        child: CupertinoButton(
          child: Text('To Splash'),
          onPressed: () => changeTest(context),
        ),
      ),
    );
  }
}



class DataStream {
  StreamController _streamController;

  StreamSink<bool> get dataSink =>
      _streamController.sink;

  Stream<bool> get dataStream =>
      _streamController.stream;

  DataStream() {
    _streamController = StreamController<bool>();
  }

  dispose() {
    _streamController?.close();
  }

}



class Page2 extends StatefulWidget {

  DataStream stream;
  Page2({this.stream});

  @override
  State<StatefulWidget> createState() => new PageState();
}

class PageState extends State<Page2> {


  bool textChanger = false;
  bool firstText = true;

  Text myText() {
    if (textChanger) {
      Text text1 = new Text('Text One',
          style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
      return text1;
    } else {
      Text text1 = new Text('Text Two',
          style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
      return text1;
    }
  }

  void changeText() {
    if (!firstText) {
      if (textChanger) {
        print('Change One');
        setState(() {
          textChanger = false;
        });
      } else {
        print('Change Two');
        setState(() {
          textChanger = true;
        });
      }
    } else {
      firstText = false;
    }
  }


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Container(
          child: Center(
              child: myText()
          )
      ),);
  }

  @override
  void initState() {
    super.initState();
    widget.stream.dataStream.listen((onData) {
      changeText();
    });
  }


}

【讨论】:

    【解决方案3】:

    由于无法完全掌握您的问题,我只想声明,每次我自己使用“正常”流时,我都非常头疼,RxDart 就像阿司匹林一样流的世界对我来说:) 不确定这是否是你正在寻找的答案,但我想我还是会发布它 - 你永远不知道!

    【讨论】:

      【解决方案4】:

      啊哈!我设法弄清楚了。 (感谢大家的帮助,真的非常感谢)。这实际上是一个非常愚蠢的菜鸟错误,我一退后一步就看到了。

      你会注意到在 main.dart 文件中有一行

      DataStream stream = DataStream();
      

      我将此设置为全局变量。因此,程序的任何部分都可以根据需要访问信息。这是我需要设置的方式......但我忘记了它可以被实例化。

      所以我所做的就是将其更改为:

      DataStream stream;
      

      然后在我的 main.dart 文件中,就在推送导航器之前,我添加了一行

      stream = new DataStream();
      Navigator.push(context, CupertinoPageRoute(builder: (context) => Page2(stream: stream)));
      

      所以现在我正在创建流的一个新实例,在它从程序的早期位中正确处理之后。 磕头。一周前应该想通了。

      【讨论】:

        【解决方案5】:

        Streams 只能有一个监听器。广播流可以有很多监听器。

        如果/当您尝试向常规流添加第二个侦听器时,您会遇到异常。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-03-06
          • 2021-07-04
          • 2018-09-01
          • 2019-03-23
          • 2023-02-16
          • 2023-03-29
          • 2018-12-26
          • 1970-01-01
          相关资源
          最近更新 更多