Flutter:未处理的异常:坏状态:在调用close之后无法添加新事件(与先前情况不同)

3
我正在尝试使用BLoC模式来管理API的数据并在我的小部件中显示它们。 我能够从API获取数据并处理并显示它,但我正在使用底部导航栏,并且当我更改选项卡并返回到以前的选项卡时,它会返回以下错误:
未处理的异常:坏状态:调用close后无法添加新事件。
我知道这是因为我正在关闭流,然后尝试添加到它,但我不知道如何修复它,因为不处置发布主题将导致内存泄漏。
我知道可能这个问题几乎与这个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();

这是我的UI代码:

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的存储库。

3个回答

1

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

由于 bloc 是在 UI 代码外部初始化的,因此无需将其释放。


但是如果我不实现bloc.dispose();它会导致内存泄漏。这是我使用BLoC的参考:https://medium.com/flutterpub/architect-your-flutter-project-using-bloc-pattern-part-2-d8dd1eca9ba5 - R Rifa Fauzi Komara
因为他只使用单个页面,而您有多个(bottomNavBar)。一个解决方案是将bloc dispose放在上面的父部件中。 - Willy
你能为你的解决方案编写代码吗?因为我对实现感到困惑。抱歉。 - R Rifa Fauzi Komara

1

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

你必须将bloc实例添加到你的widget树中,在某个InheritedWidget中使用一些Provider逻辑。然后在你的widget树下面,你会拿到那个实例并访问它的streams。这就是为什么整个过程被称为“状态提升”的原因。

这样,当你需要它时,你的bloc将始终存在,并且dispose也会在某个时候被调用。

例如,一个bloc provider:

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(因此可以被处理)。

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

在我的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,
            );
          }
      );
  }
}

通过路由,bloc在我们的homePage上方被创建。在这里,我可以调用我想要的任何初始化方法,比如.fetchAllMeals('Dessert'),而不需要使用StatefulWidget并在initState中调用它。
现在显然,为了使其工作,您的blocs必须实现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()的覆盖,从现在开始,您的bloc将自行处理,只需确保在此方法中关闭所有内容。

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

最后,在您的DesertScreen小部件的构建方法中,可以像这样获取可用的bloc实例:

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

使用这种方法的简单项目在这里


谢谢你提供的解决方案,但我对如何实现它感到困惑。因为我是Flutter和实现BLoC模式的新手。你能否用我的代码重新编写你的解决方案? - R Rifa Fauzi Komara

-2

针对解决我的问题的答案,您可以通过以下链接查看:这里

希望您喜欢!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接