web-dev-qa-db-fra.com

flutter: non géré Exception: mauvais état: impossible d'ajouter de nouveaux événements après avoir appelé close

J'essaie d'utiliser le modèle de bloc pour gérer les données d'une API et les afficher dans mon widget. Je suis capable de récupérer des données de l'API et de les traiter et de les afficher, mais j'utilise une barre de navigation inférieure et lorsque je change d'onglet et que je passe à mon onglet précédent, il renvoie cette erreur:

Exception non gérée: mauvais état: impossible d'ajouter de nouveaux événements après avoir appelé close.

Je sais que c'est parce que je ferme le flux et que j'essaye d'y ajouter, mais je ne sais pas comment le corriger car ne pas supprimer le publishsubject entraînera memory leak. voici mon code 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: () {},
        );
      },
    ),
  );
}

voici mon code bloc:

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

Je n'ai pas inclus le repo et le code API car il ne fait pas l'objet de cette erreur.

12
geekymano

Si l'erreur est en fait causée par le code que vous avez publié, je voudrais simplement ajouter une vérification pour vous assurer qu'aucun nouvel événement n'est ajouté après l'appel de 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();
9

Utilisation StreamController.isClosed pour vérifier si le contrôleur est fermé ou non, s'il n'est pas fermé, ajoutez-y des données.

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

Depuis Docs:

Si le contrôleur de flux est fermé pour ajouter plus d'événements.

Le contrôleur se ferme en appelant la méthode close. De nouveaux événements ne peuvent pas être ajoutés, en appelant add ou addError, à un contrôleur fermé.

Si le contrôleur est fermé, l'événement "terminé" n'est peut-être pas encore livré, mais il a été planifié et il est trop tard pour ajouter d'autres événements.

6
CopsOnRoad

Je rencontre la même erreur et j'ai remarqué que si vous cochez isClosed, l'écran n'est pas mis à jour. Dans votre code, vous devez supprimer la dernière ligne du fichier Bloc:

ServiceBloc serviceBloc = new ServiceBloc();

et mettez cette ligne dans CategoryPage juste avant initState (). De cette façon, votre widget crée et supprime le bloc. Auparavant, le widget ne supprime que le bloc, mais il n'est jamais recréé lorsque le widget est recréé.

2
cwhisperer

@cwhisperer a absolument raison. Initialisez et disposez votre bloc à l'intérieur du widget comme ci-dessous.

final ServiceBloc serviceBloc = new ServiceBloc();

  @override
  void initState() {
    serviceBloc.getAllServices();
    super.initState();
  }

  @override
  void dispose() {
    serviceBloc.dispose();
    super.dispose();
  }

et supprimez ServiceBloc serviceBloc = new ServiceBloc(); de votre class ServiceBloc

1
Lutfor Rahman