flutter: Unhandled Exception: Bad state: Cannot add new events after calling close

geekymano picture geekymano · Apr 5, 2019 · Viewed 19.4k times · Source

I am trying to use the bloc pattern to manage data from an API and show them in my widget. I am able to fetch data from API and process it and show it, but I am using a bottom navigation bar and when I change tab and go to my previous tab, it returns this error:

Unhandled Exception: Bad state: Cannot add new events after calling close.

I know it is because I am closing the stream and then trying to add to it, but I do not know how to fix it because not disposing the publishsubject will result in memory leak. here is my Ui code:

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: () {},
        );
      },
    ),
  );
}

here is my bloc code:

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();

I did not include the repo and API code because it is not in the subject of this error.

Answer

CopsOnRoad picture CopsOnRoad · Oct 2, 2019

Use StreamController.isClosed to check if the controller is closed or not, if not closed add data to it.

if (!_controller.isClosed) 
  _controller.sink.add(...); // safe to add data as _controller isn't closed yet

From Docs:

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.