Flutter:未处理的异常:错误状态:调用关闭后无法添加新事件(不一样的情况)

Posted

技术标签:

【中文标题】Flutter:未处理的异常:错误状态:调用关闭后无法添加新事件(不一样的情况)【英文标题】:Flutter: Unhandled Exception: Bad state: Cannot add new events after calling close (NOT SAME CASE) 【发布时间】:2019-10-29 22:48:41 【问题描述】:

我正在尝试使用 BLoC 模式来管理来自 API 的数据并将它们显示在我的小部件中。我能够从 API 获取数据并对其进行处理和显示,但我使用的是底部导航栏,当我更改选项卡并转到上一个选项卡时,它返回此错误:

未处理的异常:错误状态:调用关闭后无法添加新事件。

我知道这是因为我正在关闭流然后尝试添加到它,但我不知道如何修复它,因为不处理发布主题会导致内存泄漏。

我知道这个问题可能和这个question差不多。

但是我已经实现了它,但它在我的情况下不起作用,所以我用不同的代码提出问题,希望有人可以帮助我解决我的情况。希望你能理解,谢谢。

这是我的 BLoC 代码:

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

这是我的用户界面代码:

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


如果您需要完整的源代码,这是带有分支submission-3 的仓库

【问题讨论】:

【参考方案1】:

对于解决我的问题的答案,您可以点击以下链接:This

希望你喜欢!!

【讨论】:

【参考方案2】:

你为什么要在 bloc 类上实例化你的 bloc?

您必须将您的 bloc 实例添加到您的小部件树中的某个位置,使用带有一些 Provider 逻辑的 InheritedWidget。然后在树下的小部件中,您将获取该实例并访问其流。这就是为什么这整个过程被称为“提升国家”。

这样,你的 bloc 在你需要的时候总是存在的,并且 dispose 仍然会在某个时候被调用。

例如一个集团供应商:

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;

它结合了InheritedWidget(可以在小部件树中轻松使用)和StatefulWidget(因此它可以是一次性的)。

现在您必须将某个块的提供者添加到您的小部件树的某个位置,这取决于您,我个人喜欢将它添加到我的屏幕路由之间。

在我的MaterialApp 小部件的溃败中:

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

在路线的帮助下,该集团是在我们的homePage 的“上方”创建的。在这里,我可以调用我想要的 bloc 上的任何初始化方法,例如 .fetchAllMeals('Dessert'),而无需使用 StatefulWidget 并在 initState 上调用它。

现在显然,要让这个工作你的集团必须实现 BlocBase

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

注意dispose() 上的覆盖,从现在开始,您的集团将自行处理,只需确保关闭此方法上的所有内容。

这里有一个使用这种方法的简单项目。

为此,在 DesertScreen 小部件的 build 方法上,获取可用的 bloc 实例,如下所示:

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

一个使用这种方法的简单项目here。

【讨论】:

感谢您给我一个解决方案,但我对实施它感到困惑。因为我是 Flutter 和实现 BLoC 模式的新手。你能用我的代码重写你的解决方案吗?【参考方案3】:

bloc.dispose(); 是问题所在。

由于 bloc 在您的 UI 代码之外初始化,因此无需释放它们。

【讨论】:

但是如果我不实现 bloc.dispose();这将导致内存泄漏。这是我使用 BLoC 的参考:medium.com/flutterpub/… 因为他只使用单页,而你有多个(bottomNavBar)。一种解决方案是将 bloc dispose 放在页面上方的父小部件上。 您可以为您的解决方案编写代码吗?因为我对实施感到困惑。对不起

以上是关于Flutter:未处理的异常:错误状态:调用关闭后无法添加新事件(不一样的情况)的主要内容,如果未能解决你的问题,请参考以下文章

Flutter firebase 查询快照错误:“未处理的异常:错误状态:无元素”

未处理的异常:错误状态:平台不允许使用不安全的 HTTP - usesClearTextTraffic 不起作用

错误:flutter/lib/ui/ui_dart_state.cc(177) 未处理的异常:NoSuchMethodError:方法“插入”在 null 上调用

Flutter:来自后退导航的回调产生“错误状态:调用关闭后无法添加新事件”

如何解决此问题 [错误:flutter/lib/ui/ui_dart_state.cc(199)] 未处理的异常:NoSuchMethodError:getter 'phone' 被调用为 null。

Flutter 中的错误:未处理的异常:“Null”类型不是“String”类型的子类型