ブロックパターンを使用してAPIからのデータを管理し、ウィジェットに表示しようとしています。 APIからデータをフェッチして処理して表示することはできますが、下部のナビゲーションバーを使用していて、タブを変更して前のタブに移動すると、次のエラーが返されます。
未処理の例外:不正な状態: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();
このエラーの対象ではないため、リポジトリと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
ドキュメントから:
イベントを追加するためにストリームコントローラが閉じているかどうか。
コントローラは、closeメソッドを呼び出すことによって閉じられます。 addまたはaddErrorを呼び出して、閉じたコントローラーに新しいイベントを追加することはできません。
コントローラが閉じている場合、「完了」イベントはまだ配信されていない可能性がありますが、スケジュールされているため、イベントを追加するには遅すぎます。
同じエラーが発生し、isClosedをチェックすると画面が更新されないことに気付きました。コードでは、Blocファイルから最後の行を削除する必要があります。
ServiceBloc serviceBloc = new ServiceBloc();
この行をCategoryPageのinitState()の直前に置きます。このようにして、ウィジェットはブロックを作成および破棄します。以前は、ウィジェットはブロックを破棄するだけですが、ウィジェットが再作成されるときに再作成されることはありません。
@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();
を削除します