扑动:未处理的异常:错误状态:调用关闭后无法添加新事件
flutter: Unhandled Exception: Bad state: Cannot add new events after calling close
我正在尝试使用 bloc 模式来管理来自 API 的数据并在我的小部件中显示它们。我能够从 API 获取数据并处理并显示它,但我使用的是底部导航栏,当我更改选项卡并转到上一个选项卡时,它 returns 此错误:
Unhandled Exception: Bad state: Cannot add new events after calling
close.
我知道这是因为我正在关闭流然后尝试添加到它,但我不知道如何修复它,因为不处理 publishsubject
将导致 memory leak
。
这是我的 Ui 代码:
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
@override
void initState() {
serviceBloc.getAllServices();
super.initState();
}
@override
void dispose() {
serviceBloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: serviceBloc.allServices,
builder: (context, AsyncSnapshot<ServiceModel> snapshot) {
if (snapshot.hasData) {
return _homeBody(context, snapshot);
}
if (snapshot.hasError) {
return Center(
child: Text('Failed to load data'),
);
}
return CircularProgressIndicator();
},
);
}
}
_homeBody(BuildContext context, AsyncSnapshot<ServiceModel> snapshot) {
return Stack(
Padding(
padding: EdgeInsets.only(top: screenAwareSize(400, context)),
child: _buildCategories(context, snapshot))
],
);
}
_buildCategories(BuildContext context, AsyncSnapshot<ServiceModel> snapshot) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 3.0),
itemCount: snapshot.data.result.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
child: CategoryWidget(
title: snapshot.data.result[index].name,
icon: Icons.phone_iphone,
),
onTap: () {},
);
},
),
);
}
这是我的集团代码:
class ServiceBloc extends MainBloc {
final _repo = new Repo();
final PublishSubject<ServiceModel> _serviceController =
new PublishSubject<ServiceModel>();
Observable<ServiceModel> get allServices => _serviceController.stream;
getAllServices() async {
appIsLoading();
ServiceModel movieItem = await _repo.getAllServices();
_serviceController.sink.add(movieItem);
appIsNotLoading();
}
void dispose() {
_serviceController.close();
}
}
ServiceBloc serviceBloc = new ServiceBloc();
我没有包括 repo 和 API 代码,因为它不在这个错误的主题中。
如果错误实际上是由您发布的代码引起的,我将添加一个检查以确保在调用 dispose()
后没有添加新事件。
class ServiceBloc extends MainBloc {
final _repo = new Repo();
final PublishSubject<ServiceModel> _serviceController =
new PublishSubject<ServiceModel>();
Observable<ServiceModel> get allServices => _serviceController.stream;
getAllServices() async {
// do nothing if already disposed
if(_isDisposed) {
return;
}
appIsLoading();
ServiceModel movieItem = await _repo.getAllServices();
_serviceController.sink.add(movieItem);
appIsNotLoading();
}
bool _isDisposed = false;
void dispose() {
_serviceController.close();
_isDisposed = true;
}
}
ServiceBloc serviceBloc = new ServiceBloc();
使用StreamController.isClosed
检查控制器是否关闭,如果没有关闭则添加数据。
if (!_controller.isClosed)
_controller.sink.add(...); // safe to add data as _controller isn't closed yet
来自文档:
Whether the stream controller is closed for adding more events.
The controller becomes closed by calling the close method. New events cannot be added, by calling add or addError, to a closed controller.
If the controller is closed, the "done" event might not have been delivered yet, but it has been scheduled, and it is too late to add more events.
我 运行 遇到同样的错误并注意到如果您检查 isClosed,则屏幕不会更新。在您的代码中,您必须从 Bloc 文件中删除最后一行:
ServiceBloc serviceBloc = new ServiceBloc();
并将此行放在 CategoryPage 中的 initState() 之前。通过这种方式,您的小部件正在创建和处置集团。之前,widget 仅处理 bloc,但在重新创建 widget 时永远不会重新创建它。
@cwhisperer 完全正确。像下面一样在小部件中初始化和处理你的块。
final ServiceBloc serviceBloc = new ServiceBloc();
@override
void initState() {
serviceBloc.getAllServices();
super.initState();
}
@override
void dispose() {
serviceBloc.dispose();
super.dispose();
}
并从您的 class ServiceBloc
中删除 ServiceBloc serviceBloc = new ServiceBloc();
除了提供的解决方案外,我认为您还应该排空 ServiceBloc 中使用的流 allServices
:
@override
void dispose() {
...
allServices?.drain();
}
ServiceBloc serviceBloc = new ServiceBloc();
// 删除这段代码
// 不要在同一个页面中初始化 class 会导致状态不佳。
我在生产中也遇到了这个问题,我意识到我们应该在处置 Widget 时处置 BehaviorSubject(或任何其他 StreamController),或者在添加新值之前检查 Stream 是否关闭。
这是一个很好的扩展来完成所有工作:
extension BehaviorSubjectExtensions <T> on BehaviorSubject<T> {
set safeValue(T newValue) => isClosed == false ? add(newValue) : () {};
}
你可以这样使用它:
class MyBloc {
final _data = BehaviorSubject<String>();
void fetchData() {
// get your data from wherever it is located
_data.safeValue = 'Safe to add data';
}
void dispose() {
_data.close();
}
}
如何在Widget中配置:
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
late MyBloc bloc;
@override
void initState() {
bloc = MyBloc();
bloc.fetchData();
super.initState();
}
@override
void dispose() {
bloc.dispose();
super.dispose();
}
// Other part of your Widget
}
更好的是,如果您不确定在处理后不会重用该流:
call the drain() function on the stream before closing the stream.
dispose() async{
await _coinDataFetcher.drain();
_coinDataFetcher.close();
_isdisposed = true;
}
我正在尝试使用 bloc 模式来管理来自 API 的数据并在我的小部件中显示它们。我能够从 API 获取数据并处理并显示它,但我使用的是底部导航栏,当我更改选项卡并转到上一个选项卡时,它 returns 此错误:
Unhandled Exception: Bad state: Cannot add new events after calling close.
我知道这是因为我正在关闭流然后尝试添加到它,但我不知道如何修复它,因为不处理 publishsubject
将导致 memory leak
。
这是我的 Ui 代码:
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
@override
void initState() {
serviceBloc.getAllServices();
super.initState();
}
@override
void dispose() {
serviceBloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: serviceBloc.allServices,
builder: (context, AsyncSnapshot<ServiceModel> snapshot) {
if (snapshot.hasData) {
return _homeBody(context, snapshot);
}
if (snapshot.hasError) {
return Center(
child: Text('Failed to load data'),
);
}
return CircularProgressIndicator();
},
);
}
}
_homeBody(BuildContext context, AsyncSnapshot<ServiceModel> snapshot) {
return Stack(
Padding(
padding: EdgeInsets.only(top: screenAwareSize(400, context)),
child: _buildCategories(context, snapshot))
],
);
}
_buildCategories(BuildContext context, AsyncSnapshot<ServiceModel> snapshot) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 3.0),
itemCount: snapshot.data.result.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
child: CategoryWidget(
title: snapshot.data.result[index].name,
icon: Icons.phone_iphone,
),
onTap: () {},
);
},
),
);
}
这是我的集团代码:
class ServiceBloc extends MainBloc {
final _repo = new Repo();
final PublishSubject<ServiceModel> _serviceController =
new PublishSubject<ServiceModel>();
Observable<ServiceModel> get allServices => _serviceController.stream;
getAllServices() async {
appIsLoading();
ServiceModel movieItem = await _repo.getAllServices();
_serviceController.sink.add(movieItem);
appIsNotLoading();
}
void dispose() {
_serviceController.close();
}
}
ServiceBloc serviceBloc = new ServiceBloc();
我没有包括 repo 和 API 代码,因为它不在这个错误的主题中。
如果错误实际上是由您发布的代码引起的,我将添加一个检查以确保在调用 dispose()
后没有添加新事件。
class ServiceBloc extends MainBloc {
final _repo = new Repo();
final PublishSubject<ServiceModel> _serviceController =
new PublishSubject<ServiceModel>();
Observable<ServiceModel> get allServices => _serviceController.stream;
getAllServices() async {
// do nothing if already disposed
if(_isDisposed) {
return;
}
appIsLoading();
ServiceModel movieItem = await _repo.getAllServices();
_serviceController.sink.add(movieItem);
appIsNotLoading();
}
bool _isDisposed = false;
void dispose() {
_serviceController.close();
_isDisposed = true;
}
}
ServiceBloc serviceBloc = new ServiceBloc();
使用StreamController.isClosed
检查控制器是否关闭,如果没有关闭则添加数据。
if (!_controller.isClosed)
_controller.sink.add(...); // safe to add data as _controller isn't closed yet
来自文档:
Whether the stream controller is closed for adding more events.
The controller becomes closed by calling the close method. New events cannot be added, by calling add or addError, to a closed controller.
If the controller is closed, the "done" event might not have been delivered yet, but it has been scheduled, and it is too late to add more events.
我 运行 遇到同样的错误并注意到如果您检查 isClosed,则屏幕不会更新。在您的代码中,您必须从 Bloc 文件中删除最后一行:
ServiceBloc serviceBloc = new ServiceBloc();
并将此行放在 CategoryPage 中的 initState() 之前。通过这种方式,您的小部件正在创建和处置集团。之前,widget 仅处理 bloc,但在重新创建 widget 时永远不会重新创建它。
@cwhisperer 完全正确。像下面一样在小部件中初始化和处理你的块。
final ServiceBloc serviceBloc = new ServiceBloc();
@override
void initState() {
serviceBloc.getAllServices();
super.initState();
}
@override
void dispose() {
serviceBloc.dispose();
super.dispose();
}
并从您的 class ServiceBloc
ServiceBloc serviceBloc = new ServiceBloc();
除了提供的解决方案外,我认为您还应该排空 ServiceBloc 中使用的流 allServices
:
@override
void dispose() {
...
allServices?.drain();
}
ServiceBloc serviceBloc = new ServiceBloc();
// 删除这段代码 // 不要在同一个页面中初始化 class 会导致状态不佳。
我在生产中也遇到了这个问题,我意识到我们应该在处置 Widget 时处置 BehaviorSubject(或任何其他 StreamController),或者在添加新值之前检查 Stream 是否关闭。
这是一个很好的扩展来完成所有工作:
extension BehaviorSubjectExtensions <T> on BehaviorSubject<T> {
set safeValue(T newValue) => isClosed == false ? add(newValue) : () {};
}
你可以这样使用它:
class MyBloc {
final _data = BehaviorSubject<String>();
void fetchData() {
// get your data from wherever it is located
_data.safeValue = 'Safe to add data';
}
void dispose() {
_data.close();
}
}
如何在Widget中配置:
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
late MyBloc bloc;
@override
void initState() {
bloc = MyBloc();
bloc.fetchData();
super.initState();
}
@override
void dispose() {
bloc.dispose();
super.dispose();
}
// Other part of your Widget
}
更好的是,如果您不确定在处理后不会重用该流:
call the drain() function on the stream before closing the stream.
dispose() async{
await _coinDataFetcher.drain();
_coinDataFetcher.close();
_isdisposed = true;
}