2
votes

Flutter: Unhandled Exception: Mauvais état: Impossible d'ajouter de nouveaux événements après avoir appelé close (PAS MÊME CAS)

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

Exception non gérée: état incorrect: 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'essaie ensuite d'y ajouter, mais je ne sais pas comment le réparer car ne pas supprimer le sujet de publication entraînera une fuite de mémoire.

Je sais que cette question est peut-être presque la même que celle-ci question .

Mais je l'ai implémentée et cela ne fonctionne pas dans mon cas, donc je pose des questions avec un code différent et j'espère que quelqu'un pourra aidez-moi à résoudre mon cas. J'espère que vous comprenez, merci.

Voici mon code BLoC:

import 'package:flutter/material.dart';
import '../models/meals_list.dart';
import '../blocs/meals_list_bloc.dart';
import '../hero/hero_animation.dart';
import 'package:dicoding_submission/src/app.dart';
import 'detail_screen.dart';


class DesertScreen extends StatefulWidget {
  @override
  DesertState createState() => new DesertState();
}

class DesertState extends State<DesertScreen> {

  @override
  void initState() {
    super.initState();
    bloc.fetchAllMeals('Dessert');
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: getListDesert()
    );
  }

  getListDesert() {
    return Container(
      color: Color.fromRGBO(58, 66, 86, 1.0),
      child: Center(
        child: StreamBuilder(
          stream: bloc.allMeals,
          builder: (context, AsyncSnapshot<MealsList> snapshot) {
            if (snapshot.hasData) {
              return _showListDessert(snapshot);
            } else if (snapshot.hasError) {
              return Text(snapshot.error.toString());
            }
            return Center(child: CircularProgressIndicator(
                valueColor: AlwaysStoppedAnimation<Color>(Colors.white)
            ));
          },
        ),
      ),
    );
  }

  Widget _showListDessert(AsyncSnapshot<MealsList> snapshot) => GridView.builder(
    itemCount: snapshot == null ? 0 : snapshot.data.meals.length,
    gridDelegate:
    SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
    itemBuilder: (BuildContext context, int index) {
      return GestureDetector(
        child: Card(
          elevation: 2.0,
          shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(5))),
          margin: EdgeInsets.all(10),
          child: GridTile(
            child: PhotoHero(
              tag: snapshot.data.meals[index].strMeal,
              onTap: () {
                showSnackBar(context, snapshot.data.meals[index].strMeal);
                Navigator.push(
                    context,
                    PageRouteBuilder(
                      transitionDuration: Duration(milliseconds: 777),
                      pageBuilder: (BuildContext context, Animation<double> animation,
                          Animation<double> secondaryAnimation) =>
                          DetailScreen(
                              idMeal: snapshot.data.meals[index].idMeal),
                    ));
              },
              photo: snapshot.data.meals[index].strMealThumb,
            ),
            footer: Container(
              color: Colors.white70,
              padding: EdgeInsets.all(5.0),
              child: Text(
                snapshot.data.meals[index].strMeal,
                textAlign: TextAlign.center,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                    fontWeight: FontWeight.bold, color: Colors.deepOrange),
              ),
            ),
          ),
        ),
      );
    },
  );

}

Voici mon code d'interface utilisateur:

import '../resources/repository.dart';
import 'package:rxdart/rxdart.dart';
import '../models/meals_list.dart';

class MealsBloc {
  final _repository = Repository();
  final _mealsFetcher = PublishSubject<MealsList>();

  Observable<MealsList> get allMeals => _mealsFetcher.stream;

  fetchAllMeals(String mealsType) async {
    MealsList mealsList = await _repository.fetchAllMeals(mealsType);
    _mealsFetcher.sink.add(mealsList);
  }

  dispose() {
    _mealsFetcher.close();
  }
}

final bloc = MealsBloc();


0 commentaires

3 Réponses :


1
votes

bloc.dispose (); est le problème.

Puisque le bloc est initialisé en dehors de votre code d'interface utilisateur, il n'est pas nécessaire de le supprimer.


3 commentaires

Mais si je n'implémente pas bloc.dispose (); cela provoquera une fuite de mémoire. C'est ma référence pour utiliser BLoC: medium.com/flutterpub/...


Parce qu'il n'utilise qu'une seule page, alors que vous en avez plusieurs (bottomNavBar). Une solution consiste à mettre le bloc à disposition sur le widget parent au-dessus des pages.


Pouvez-vous écrire le code de votre solution? Parce que j'ai confondu pour l'implémentation. Désolé



1
votes

Pourquoi instanciez-vous votre bloc sur la classe bloc?

Vous devez ajouter votre instance de bloc quelque part dans votre arborescence de widgets, en utilisant un InheritedWidget avec une logique de fournisseur. Ensuite, dans vos widgets dans l'arborescence, vous prendriez cette instance et accéderiez à ses flux. C'est pourquoi tout ce processus s'appelle «lever l'état».

De cette façon, votre bloc sera toujours actif lorsque vous en avez besoin, et la disposition sera toujours appelée à un moment donné.

Un fournisseur de bloc par exemple:

var bloc = BlocProvider.of<MealsBloc>(context);

Il utilise une combinaison de InheritedWidget (pour être facilement disponible dans l'arborescence des widgets) et StatefulWidget (il peut donc être jetable).

Maintenant, vous devez ajouter le fournisseur d'un bloc quelque part dans votre arborescence de widgets, c'est à vous, personnellement, j'aime l'ajouter entre les routes de mes écrans.

Dans la route de mon widget MaterialApp :

class MealsBloc implements BlocBase {
  final _repository = Repository();
  final _mealsFetcher = PublishSubject<MealsList>();

  Observable<MealsList> get allMeals => _mealsFetcher.stream;

  fetchAllMeals(String mealsType) async {
    MealsList mealsList = await _repository.fetchAllMeals(mealsType);
    _mealsFetcher.sink.add(mealsList);
  }

  @override
  dispose() {
    _mealsFetcher.close();
  }
}

À l'aide des routes, le bloc a été créé "au-dessus" de notre homePage . Ici, je peux appeler n'importe où des méthodes d'initialisation sur le bloc que je veux, comme .fetchAllMeals ('Dessert') , sans avoir besoin d'utiliser un StatefulWidget et de l'appeler sur initState .

Maintenant, évidemment, pour que cela fonctionne, vos blocs doivent implémenter la classe BlocBase

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'MyApp',
      onGenerateRoute: _routes,
    );
  }

  Route _routes(RouteSettings settings) {
    if (settings.isInitialRoute)
      return MaterialPageRoute(
          builder: (context) {
            final mealsbloc = MealsBloc();
            mealsbloc.fetchAllMeals('Dessert');

            final homePage = DesertScreen();
            return BlocProvider<DesertScreen>(
              bloc: mealsbloc,
              child: homePage,
            );
          }
      );
  }
}

Remarquez le override sur dispose () , à partir de maintenant, vos blocs se débarrasseront d'eux-mêmes, assurez-vous simplement de tout fermer sur cette méthode.

Un projet simple avec cette approche ici. p>

Pour terminer cela, sur la méthode de construction de votre widget DesertScreen , récupérez l'instance disponible du bloc comme ceci:

import 'package:flutter/material.dart';
abstract class BlocBase {
  void dispose();
}

class BlocProvider<T extends BlocBase> extends StatefulWidget {
  BlocProvider({
    Key key,
    @required this.child,
    @required this.bloc,
  }) : super(key: key);

  final T bloc;
  final Widget child;

  @override
  State<StatefulWidget> createState() => _BlocProviderState<T>();

  static T of<T extends BlocBase>(BuildContext context) {
    final type = _typeOf<_BlocProviderInherited<T>>();
    _BlocProviderInherited<T> provider = context
        .ancestorInheritedElementForWidgetOfExactType(type)
        ?.widget;
    return provider?.bloc;
  }

  static Type _typeOf<T>() => T;
}

class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<T>> {
  @override
  Widget build(BuildContext context) {
    return new _BlocProviderInherited(
      child: widget.child,
      bloc: widget.bloc
    );
  }

  @override
  void dispose() {
    widget.bloc?.dispose();
    super.dispose();
  }
}

class _BlocProviderInherited<T> extends InheritedWidget {
  _BlocProviderInherited({
    Key key,
    @required Widget child,
    @required this.bloc
  }) : super(key: key, child: child);

  final T bloc;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => false;
}

Un projet simple utilisant cette approche ici .


1 commentaires

Merci de m'avoir donné une solution, mais je ne savais pas trop l'implémenter. Parce que je suis nouveau dans Flutter et l'implémentation BLoC Pattern. Pouvez-vous réécrire votre solution avec mon code?



-2
votes

Pour obtenir des réponses qui résolvent mon problème, vous pouvez suivre le lien suivant: Ceci

J'espère que vous l'apprécierez !!


0 commentaires