我正在尝试使用块模式来管理来自API的数据并将其显示在我的小部件中。我可以从API提取数据并对其进行处理并显示,但是我使用的是底部导航栏,当我更改选项卡并转到上一个选项卡时,它将返回此错误:
未处理的异常:错误状态:调用后无法添加新事件 关闭。
我知道是因为我要关闭流然后尝试对其进行添加,但是我不知道如何解决它,因为不处理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();
我没有包含回购代码和API代码,因为它不在此错误的主题之内。
答案 0 :(得分:3)
使用StreamController.isClosed
检查控制器是否关闭,如果未关闭,则向其中添加数据。
if (!_controller.isClosed)
_controller.sink.add(...); // safe to add data as _controller isn't closed yet
来自文档:
是否关闭流控制器以添加更多事件。
通过调用close方法,控制器将关闭。无法通过调用add或addError将新事件添加到封闭的控制器中。
如果控制器已关闭,则“尚未完成”事件可能尚未交付,但是已经安排好了,添加更多事件为时已晚。
答案 1 :(得分:2)
如果错误实际上是由您发布的代码引起的,我只需添加一个检查以确保在调用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();
答案 2 :(得分:2)
我遇到了同样的错误,并注意到如果选中isClosed,则不会更新屏幕。在您的代码中,您必须从Bloc文件中删除最后一行:
ServiceBloc serviceBloc = new ServiceBloc();
将此行放在initState()之前的CategoryPage中。这样,您的小部件就可以创建和处置集团。以前,该小部件仅处理该块,而在重新创建该小部件时永远不会重新创建它。
答案 3 :(得分:2)
除了提供的解决方案之外,我认为您还应该使用以下方法耗尽ServiceBloc中使用的流allServices
:
@override
void dispose() {
...
allServices?.drain();
}
答案 4 :(得分:1)
@cwhisperer是完全正确的。像下面这样将块初始化并放置在小部件中。
final ServiceBloc serviceBloc = new ServiceBloc();
@override
void initState() {
serviceBloc.getAllServices();
super.initState();
}
@override
void dispose() {
serviceBloc.dispose();
super.dispose();
}
并从您的ServiceBloc serviceBloc = new ServiceBloc();
删除class ServiceBloc
答案 5 :(得分:1)
ServiceBloc serviceBloc = new ServiceBloc();
//删除此代码 //不要在同一页面上初始化会导致状态错误的类。