Flutter DataStream 未正确关闭和重建。 [错误状态:流已被收听。]
Flutter DataStream not closing and re-building properly. [Bad state: Stream has already been listened to.]
好的,所以我知道可以使用广播系统制造一个流来多次收听一个流,但这不是我在这里尝试做的。
我也在编辑这个,因为我收到的一个答案目前无法解决我的问题,因此非常感谢任何帮助。
实际上由于某种原因,我的代码并没有完全删除流,如果重新使用,它会尝试重新收听已经收听和关闭的同一流,none 其中有效(显然)。我没有尝试再次收听同一个流,而是尝试创建一个新的流来收听。 (删除并清除原始第一流中的所有信息)。
原文post 下面继续:
我正在使用 DataStream 模板将数据从程序的各个部分流式传输到 and/or,但我不确定如何纠正这个问题。我确定这是一个愚蠢的 newb 错误,但我没有充分使用 DataStreams 来理解为什么会发生这种情况。
请不要误会我的意思,我的程序的单个循环运行得非常好,完全没有问题。但是,一旦我完成了程序的一个循环,如果我尝试第二次,我会收到错误消息:
错误状态:流已被收听。
因此我知道我的程序没有创建新流,而是尝试重新使用原始流,而且我不是 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();
});
}
}
实际上,在此示例中,您可以单击文本,然后转到第二页,这将在提示时正确更改文本,完成后 return 将转到原始页面。那将是我程序的一个 "Cycle"。
您可以看到该程序随后立即处理流。
问题是,如果我第二次单击该文本,它仍在尝试收听原始流,而不是创建一个全新的流并重新开始。
为什么?我该如何解决这个问题?
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 class.
我要做的是将流移动到 StatefulWidget
并在 "to Splash" tap
上重新创建它
在实际情况下,将其放入小部件树中的有状态小部件,所有需要访问的小部件都可以找到它(在您的情况下甚至比导航器更高)。
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();
});
}
}
我无法完全理解您的问题,我只想说每次我自己使用 "normal" 流和 RxDart 对我来说就像流媒体世界中的阿司匹林 :) 不确定这是否是您正在寻找的答案,但我想我还是 post 它 - 你永远不知道!
啊哈!我设法弄明白了。 (感谢大家的帮助,真的非常感谢)。刚退一步就看到了,其实是一个非常愚蠢的菜鸟错误。
您会在 main.dart 文件中注意到我有行
DataStream stream = DataStream();
我将其设置为全局变量。因此程序的任何部分都可以根据需要访问信息。这是我有点需要设置它的方式......但我忘记了它可以被实例化。
所以我将其更改为:
DataStream stream;
然后在我的 main.dart 文件中,就在推送导航器之前,我添加了行
stream = new DataStream();
Navigator.push(context, CupertinoPageRoute(builder: (context) => Page2(stream: stream)));
所以现在我正在创建一个新的流实例,在它从程序的早期位中被正确处理之后。 拍头。应该在一周前就想通了。
Streams 只能有一个监听器。广播流可以有很多听众。
您会遇到异常 if/when 您尝试向常规流中添加第二个侦听器。
好的,所以我知道可以使用广播系统制造一个流来多次收听一个流,但这不是我在这里尝试做的。
我也在编辑这个,因为我收到的一个答案目前无法解决我的问题,因此非常感谢任何帮助。
实际上由于某种原因,我的代码并没有完全删除流,如果重新使用,它会尝试重新收听已经收听和关闭的同一流,none 其中有效(显然)。我没有尝试再次收听同一个流,而是尝试创建一个新的流来收听。 (删除并清除原始第一流中的所有信息)。
原文post 下面继续:
我正在使用 DataStream 模板将数据从程序的各个部分流式传输到 and/or,但我不确定如何纠正这个问题。我确定这是一个愚蠢的 newb 错误,但我没有充分使用 DataStreams 来理解为什么会发生这种情况。
请不要误会我的意思,我的程序的单个循环运行得非常好,完全没有问题。但是,一旦我完成了程序的一个循环,如果我尝试第二次,我会收到错误消息:
错误状态:流已被收听。
因此我知道我的程序没有创建新流,而是尝试重新使用原始流,而且我不是 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();
});
}
}
实际上,在此示例中,您可以单击文本,然后转到第二页,这将在提示时正确更改文本,完成后 return 将转到原始页面。那将是我程序的一个 "Cycle"。
您可以看到该程序随后立即处理流。
问题是,如果我第二次单击该文本,它仍在尝试收听原始流,而不是创建一个全新的流并重新开始。
为什么?我该如何解决这个问题?
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 class.
我要做的是将流移动到 StatefulWidget
并在 "to Splash" tap
在实际情况下,将其放入小部件树中的有状态小部件,所有需要访问的小部件都可以找到它(在您的情况下甚至比导航器更高)。
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();
});
}
}
我无法完全理解您的问题,我只想说每次我自己使用 "normal" 流和 RxDart 对我来说就像流媒体世界中的阿司匹林 :) 不确定这是否是您正在寻找的答案,但我想我还是 post 它 - 你永远不知道!
啊哈!我设法弄明白了。 (感谢大家的帮助,真的非常感谢)。刚退一步就看到了,其实是一个非常愚蠢的菜鸟错误。
您会在 main.dart 文件中注意到我有行
DataStream stream = DataStream();
我将其设置为全局变量。因此程序的任何部分都可以根据需要访问信息。这是我有点需要设置它的方式......但我忘记了它可以被实例化。
所以我将其更改为:
DataStream stream;
然后在我的 main.dart 文件中,就在推送导航器之前,我添加了行
stream = new DataStream();
Navigator.push(context, CupertinoPageRoute(builder: (context) => Page2(stream: stream)));
所以现在我正在创建一个新的流实例,在它从程序的早期位中被正确处理之后。 拍头。应该在一周前就想通了。
Streams 只能有一个监听器。广播流可以有很多听众。
您会遇到异常 if/when 您尝试向常规流中添加第二个侦听器。