Flutter bloc Cubit Bad state: Cannot emit new states after calling close
Asked Answered
B

2

5

I have an app that I build using Cubit I have two pages A and B.every thing works fine on its own. I use a change status cubit on both pages but when I move to the second page and pop to return to the first page I see the error on the title. I inject dependencies using get it

route A

routes: {
        '/home': (context) => MultiBlocProvider(providers: [
              BlocProvider<ChangeStatusCubit>(
                create: (context) => locator<ChangeStatusCubit>(),
              ),
            ], child: const TodoHomePage()),

Route B

'/details': (context) => MultiBlocProvider(
            providers: [
              BlocProvider<ChangeStatusCubit>(
                create: (context) => locator<ChangeStatusCubit>(),
              ),
            ],
            child: TodoDetailsPage(),

dependency injection

locator.registerLazySingleton<ChangeStatusCubit>(() => ChangeStatusCubit(
        locator(),
      ));

cubit

changeStatus(int id) async {
    emit(ChangeStatusLoading());
    try {
      ResponseModel response = await _changeStatusUseCase(id);
      if (response.status == 200) {
        emit(ChangeStatusLoaded(response.data));
      } else {
        emit(ChangeStatusError(response.error?.todo?.first ?? ""));
      }
    } catch (e) {
      emit(ChangeStatusError(e.toString()));
    }
  }
Bookmark answered 10/10, 2022 at 14:43 Comment(0)
P
11

When you use create to initialize the BlocProvider's bloc, the bloc's stream will be closed when that BlocProvider is unmounted.

To solve this, you can either move your BlocProvider higher up in the widget tree, so that it will remain mounted between pages, or you can use the BlocProvider.value constructor.

The latter is probably best, since you aren't actually creating a bloc, but making use of a pre-existing singleton. Though it would also make sense to move the widget higher up, so that it's a parent of the Navigator - that way you can declare it just once and reduce code duplication.

However, keeping the structure in your example, your code might look something like this:

routes: {
    '/home': (context) => MultiBlocProvider(
        providers: [
            BlocProvider.value<ChangeStatusCubit>(
                value: locator<ChangeStatusCubit>(),
            ),
        ],
        child: const TodoHomePage(),
    ),
    '/details': (context) => MultiBlocProvider(
        providers: [
            BlocProvider<ChangeStatusCubit>.value(
                value: locator<ChangeStatusCubit>(),
            ),
        ],
        child: TodoDetailsPage(),
    ),
}
Pater answered 10/10, 2022 at 15:44 Comment(0)
T
0

I am not a very skilled "Flutterrer" so I was struggling with a similar matter for a long time, but in a slightly different setting. I hope that this description will help someone.

My app uses several cubits. Some of them are "provided" within the main.dart method (see snippet below), some are provided locally within given screens and widgets:

main.dart

runApp(
      MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (_) => ConnectionService()..start()),
          ],
        child: MultiBlocProvider(
          providers: [
            BlocProvider(
              create: (_) => GetIt.I.get<MessagesCubit>()..fetchMessages(),
            ),
            ....
            BlocProvider(create: (_) => GetIt.I.get<JobListCubit>()),
            BlocProvider(create: (_) => GetIt.I.get<LogoutCubit>()),
          ],
          child: MaterialApp(
            navigatorKey: navigatorKey,
            title: 'MyAPP',
            theme: ThemeData(
                   ...                ),
            initialRoute: AuthRouter.landing,
            onGenerateRoute: Routes.router.generator,
            builder: (context, child) {
              return GlobalErrorHandlerWidget(child: child!);
            },
          ),
        ),
      ),
    ),

My navigation is handled by Fluro router like this one:

home_router.dart

class HomeRouter {
  HomeRouter(this.router);

  final FluroRouter router;

  static const String homePage = "/home-page";
  static const String jobScreenViewCreator = "/job-screens-view-creator";

  void register() {
    router.define(
      homePage,
      handler: Handler(
        handlerFunc: (
          BuildContext? context,
          Map<String, List<String>> params,
        ) {
          return MultiBlocProvider(
            providers: [
              //// BlocProvider(
              ////   create: (_) => GetIt.I.get<JobListCubit>(),
              //// ),

              BlocProvider(
                create: (_) => GetIt.I.get<JobDescriptionListCubit>(),
              ),
            ],
            child: const HomePage(),
          );
        },
      ),
      transitionType: TransitionType.native,
    );

    router.define(
      jobScreenViewCreator,
      handler: Handler(
        .....
    );
  }
}

Please note the commented BlocProvider in the homepage router above. This was the offending code causing the state error, since it was conflicting with the Cubit initialization on the home page. Please note that the suggestion to use didChangeDependencies instead of initState did not work for me. The code below works fine.

home_page.dart

 class HomePage extends StatefulWidget {
      const HomePage({Key? key}) : super(key: key);
    
      @override
      HomePageState createState() => HomePageState();
    }
    
    class HomePageState extends State<HomePage> {
     
  @override
  void initState() {
    _onInit();
    super.initState();
  }

  void _onInit() async {
    await BlocProvider.of<JobListCubit>(context).fetchJobs();
  }
    
      
    
      @override
      Widget build(BuildContext context) {
        return BlocBuilder<JobListCubit, JobListState>(
                            builder: (context, state) {
                          ...
                          }
                  
Thug answered 5/10, 2023 at 16:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.