Bloc: Bad state: Cannot add new events after calling close

Created on 9 Jan 2019  路  75Comments  路  Source: felangel/bloc

I can't trace the issue on my code, not sure it's my code problem or the library

StateError: Bad state: Cannot add new events after calling close
  File "broadcast_stream_controller.dart", line 253, in _BroadcastStreamController.add
  File "subject.dart", line 124, in Subject._add
  File "subject.dart", line 118, in Subject.add
  File "bloc.dart", line 73, in Bloc._bindStateSubject.<fn>
  File "stream.dart", line 816, in Stream.forEach.<fn>.<fn>
  File "stream_pipe.dart", line 11, in _runUserCode
  File "stream.dart", line 816, in Stream.forEach.<fn>
  File "zone.dart", line 1132, in _rootRunUnary
  File "zone.dart", line 1029, in _CustomZone.runUnary
  File "zone.dart", line 931, in _CustomZone.runUnaryGuarded
  File "stream_impl.dart", line 336, in _BufferingStreamSubscription._sendData
  File "stream_impl.dart", line 263, in _BufferingStreamSubscription._add
  File "broadcast_stream_controller.dart", line 379, in _SyncBroadcastStreamController._sendData
  File "broadcast_stream_controller.dart", line 291, in _BroadcastStreamController._add
  File "zone.dart", line 1132, in _rootRunUnary
  File "zone.dart", line 1029, in _CustomZone.runUnary
  File "zone.dart", line 931, in _CustomZone.runUnaryGuarded
  File "stream_impl.dart", line 336, in _BufferingStreamSubscription._sendData
  File "stream_impl.dart", line 591, in _DelayedData.perform
  File "stream_impl.dart", line 707, in _StreamImplEvents.handleNext
  File "stream_impl.dart", line 667, in _PendingEvents.schedule.<fn>
  File "zone.dart", line 1120, in _rootRun
  File "zone.dart", line 1021, in _CustomZone.run
  File "zone.dart", line 923, in _CustomZone.runGuarded
  File "zone.dart", line 963, in _CustomZone.bindCallbackGuarded.<fn>
  File "zone.dart", line 1124, in _rootRun
  File "zone.dart", line 1021, in _CustomZone.run
  File "zone.dart", line 923, in _CustomZone.runGuarded
  File "zone.dart", line 963, in _CustomZone.bindCallbackGuarded.<fn>
  File "schedule_microtask.dart", line 41, in _microtaskLoop
  File "schedule_microtask.dart", line 50, in _startMicrotaskLoop
question

Most helpful comment

@rrifafauzikomara you need to remove final bloc = MealsDetailBloc(); from your detail bloc and instead in your widget do something like:

final bloc = MealsDetailBloc();

@override
void initState() {
  super.initState();
  bloc.fetchDetailMeals(widget.idMeal);
}

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

This way your widget is creating and disposing the bloc. Before, the widget only disposes the bloc but it is never re-created when the widget is re-created. Hope that helps 馃憤

All 75 comments

I don鈥檛 think this stack trace is coming from the bloc library but in any case the problem is you are closing your stream and later trying to add to it.

Let me know if that helps. Alternatively, it would be easier for me to help if you shared the code.

Closing this for now. Feel free to comment with more details and I will reopen it.

Is it possible to determine whether the stream is closed before dispatching any event?

No but why do you need to? The only way for the stream to be closed is if you called the bloc鈥檚 dispose method.

I'm getting this error message probably due to some how the stream is closed while waiting some API to done, and once the API is done, it will dispatch an event and then causes this error

How are you disposing the bloc? Can you share any code?

Sorry, I can't share my code for now. But I will try to explain.

I did not call any bloc's dispose() method. I got a TabBar with 3 tabs, each tab has it's own bloc. When I push to a new page from one of the tabs, sometimes the error triggered. But I've noticed that every time I pop from the new page back to the tab screen, the tab screen some how get rebuild.

Are your blocs being created by a statefulwidget? They should be part of a statefulwidget so that they aren鈥檛 recreated on rebuilds.

My blocs are final in StatefulWidget

Is it possible to create a simplified version that illustrates the problem? It would make it much easier to help you.

I will try to construct an example to determine whether the issue will occur. If it doesn't, it might be indicate problem on my code otherwise I will give you the example

Perfect, thanks!

One question, does the final bloc not dispose until the dispose() method is called although the StatefulWidget rebuild?

Yup, you need to override dispose in the statefulwidget and call the bloc鈥檚 dispose.

Alright thanks! I'll give it a shot.

Hi,

Firstly, really great work with the library!

I am facing the same error but the reason i believe is not the framework but flutter itself. After Material page route, all the pages on the stack are re built. Issue for reference #14124 , #11655 . Dont know the ETA for the fix.

Streams are closed properly on navigation when on onDispose is called. But when pages on stack are rebuilt than this issue is produced. If you can add check to detect dispose is called before dispatching the event than this error can be avoided. I tried adding boolean checks but that did not help.

Please let me know if i missed something out.

Hi @MohsinN thanks for the positive feedback! 馃憤

I looked at the issues you linked and it seems like the flutter team does not view that behavior as a bug (a widget should be able to be rebuilt every frame).

It would be really helpful if you can provide me with a simple flutter app which illustrates the problem.

Hi, I'm having the same issue!

I just started using flutter about a week ago and I have a 5 week time constraint for a big project, therefore I'm really sorry if it's just a silly mistake I can't figure out!

I got a TabView with three individual pages (stateful widget each) and each one receives data from an api therefore I'm creating a bloc on each of these pages.

I got two blocs so far, one is being used in two pages, let's call them A and B, and C with its own. It was one bloc covering the three pages but the issue appeared on the page C and me trying to debug it made me create another bloc thinking the dispose of the same bloc was being an issue in between pages, but as a result this individual C bloc is still receiving the issue!

This is my page C:

`class SupplementPlansPage extends StatefulWidget {
final SupplementPlanRepository supplementPlanRepository;

SupplementPlansPage({Key key, this.supplementPlanRepository})
: assert(supplementPlanRepository != null),
super(key: key);

@override
_SupplementPlansPageState createState() => _SupplementPlansPageState();
}

class _SupplementPlansPageState extends State {
SupplementPlanBloc _planBloc;

@override
void initState() {
super.initState();
_planBloc = SupplementPlanBloc(supplementPlanRepository: widget.supplementPlanRepository);
_planBloc.dispatch(FetchSupplementServings(clientId: 1));
}

@override
Widget build(BuildContext context) {
return Stack(children: [
Container(
color: Color.fromRGBO(70, 79, 153, 1),
height: 80,
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
elevation: 2.0,
child: BlocBuilder(
bloc: _planBloc,
builder: (_, SupplementPlanState state) {
if (state is SupplementPlanLoading)
return Center(child: CircularProgressIndicator());
if (state is SupplementServingsLoaded) {
final supplementIntakeTimeList = new List();
for(SupplementIntakeTime time in state.supplementIntakeTimeList.supplementIntakeTimes){
if(time.supplementServings.length > 0){
supplementIntakeTimeList.add(time);
}
}
return _buildSupplementIntakeTimeTitles(supplementIntakeTimeList);
}
if (state is SupplementPlanError)
return Text(
'Something went wrong!',
style: TextStyle(color: Colors.red),
);
},
),
),
),
]);
}

//Builds the listview
Widget _buildSupplementIntakeTimeTitles(List supplementIntakeTimeList) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: ListView.builder(
itemBuilder: (context, index) {
return _buildRow(supplementIntakeTimeList, index);
},
itemCount: supplementIntakeTimeList.length, // The amount of items being displayed
),
),
);
}

Widget _buildRow(List supplementIntakeTimeList, int index) {
return new SupplementIntakeTimeRow(supplementIntakeTimeList, index);
}

@override
void dispose() {
_planBloc.dispose();
super.dispose();
}
}`

Worth saying that the three pages had with AutomaticKeepAliveClientMixin and the issue was present. I took it out for page C and issue is still present

Thanks in advance!

@JoseGeorges8 would it be possible to share the code with me? It would be much easier for me to help if you give me access to an application that has the problem you're running into. Thanks! 馃憤

Hey thanks for the quick response!

Unfortunately, I'm not allowed to add you as a collaborator, looking at the page where the issue is happening, is there any ideas you might think I could go ahead and jump on?

Thank you for understanding!

No problem!

This issue happens if the Bloc is disposed before an event is dispatched. I would recommend initializing and disposing your blocs one level higher than they currently are.

You can confirm that this is the issue by commenting out all the places where you call bloc.dispose() and verifying that you are no longer seeing the error. Let me know if that helps! 馃憤

I'll try right now thanks!

So I initialized the two blocs inside the TabView that's hosting the pages and passed them like this:

`
@override
void initState() {

planBloc = PlanBloc(planRepository: planRepository);
supplementPlanBloc = SupplementPlanBloc(supplementPlanRepository: supplementPlanRepository);

mealPlansPage = MealPlansPage(planRepository: planRepository, planBloc: planBloc,);
workoutPlansPage = WorkoutPlansPage(planRepository: planRepository, planBloc: planBloc);
supplementPlansPage = SupplementPlansPage(supplementPlanRepository: supplementPlanRepository, supplementPlanBloc: supplementPlanBloc);
pages = [mealPlansPage, workoutPlansPage, supplementPlansPage];
currentPage = workoutPlansPage;
super.initState();
tabController = TabController(length: 3, vsync: this);

}

@override
void dispose() {
tabController.dispose();
planBloc.dispose();
supplementPlanBloc.dispose();
super.dispose();
}
`

However the problem persist. It keeps on jumping from my StateLoading to my StateError (implemented the same way as your weather tutorial!) on page C, but the error is not being logged anymore. Also I took the with AutomaticKeepAliveClientMixin from every page in case it was affecting!

@JoseGeorges8 is there any way you can make a new super basic flutter app that has the same setup as your current app and has the same problem and share that with me?

You can just have a bunch of dummy blocs that don't really do anything and a bunch of empty pages. I just think having a concrete app that I can run and look at will make debugging your issue much easier. Thanks!

ok I'll try to replicate it and get back to you!

Hey @felangel , sorry for the wait!

So I did a basic example of the pages where I'm implementing the bloc:

https://github.com/JoseGeorges8/bloc_issue

unfortunately, the bug does not happen in here! The difference right now between this and my project is layout as well as mine has 2 blocs right now, So I'll try to add the changes and see if the bug appears!

@josegeorges8 no worries! Keep me posted 馃憤

Quick update! I did some changes to the code to mimic more my project and the issue appeared!

I added a new event and a new state, tab1 dispatches a different event than tab2 and 3 and expects a different state than 2 and 3. Now is visible that when tab1 gets initialized, the state being transitioning is not the correct?

Take a look and let me know thanks!

Awesome! I鈥檒l have a look in the next hour 馃槃

@JoseGeorges8 can you please describe the error and how I can reproduce it in the sample app?

Yes!

  1. Run the app
  2. Navigate from tab1 to tab3
  3. Back to tab1

You'll see a message displayed for when a state from a different event is received.

@JoseGeorges8 ah this is because initState can be called in any order so in this case you start off with Tab1 being active (initState called) so the fetchInfo event is dispatched. Then when you navigate to Tab3, the initState for Tab3 is called first so we dispatch the second fetchInfo but after that the initState for Tab2 is called which changes the shared bloc state to the second state and since initState is only called once when you navigate back to Tab1 initState is not called and the state does not change so you are left in the state for Tab2. Does that make sense?

Sort of! How would I solve this problem then?

Check out https://github.com/JoseGeorges8/bloc_issue/pull/1

I'd recommend having a bloc for each tab state along with AutomaticKeepAliveClientMixin to reduce the number of rebuilds.

Let me know if that helps!

Looks great! I'll test it inside my app tomorrow and I'll let you now

Thank you Felix!

@JoseGeorges8 one other thing I noticed was Equatable wasn't being used properly. I'm about to open one more PR to fix the equatable usage and that should eliminate all rebuilds

@JoseGeorges8 one last PR to fix the equatable usage https://github.com/JoseGeorges8/bloc_issue/pull/2

Hey Felix! just wanted to let you know that your solution worked!

Thanks for the help

@JoseGeorges8 Glad I was able to help 馃帀

@felangel still The Problem happens on Navigation,when i commented out the dispose()... overrides in a stateful widget,it doesn't happen!

@rajeshjeshar that means your bloc is being disposed too soon. I would recommend making sure the bloc is disposed by the widget that created it in order to avoid having a situation where a bloc is disposed too soon.

In my code I found the problem. It was the await statement before Navigator.

Produce the same error

   await  Navigator.push(
      context,
      MaterialPageRoute(
        builder: (BuildContext context) {
          return AppointmentSummaryScreen(
            AppointmentSummaryBloc(
              AppointmentRepository(),
            ),
            template,
            trainer,
            time,
          );
        },
      ),
    );

Produce no error

    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (BuildContext context) {
          return AppointmentSummaryScreen(
            AppointmentSummaryBloc(
              AppointmentRepository(),
            ),
            template,
            trainer,
            time,
          );
        },
      ),
    );

Good morning guys,
I've the same problem explained in this issue:
StateError (Bad state: Cannot add new events after calling close)
I think my problem is similar to the @JoseGeorges8 problem. I have a drawer that provide to navigate on all the App pages. In page 1 and page 2 I need to fetch the same data. In this case I need to call myblock.fetchAllData() on all the two pages initState(), then always in the same 2 pages on dispose() method I call myBlock.dispose(). I haven't understand very well because I can't re-add a new event after the dispose. For me is not clear which type of changes I need to do for fix my code remaining in the bloc pattern side.

@enricobenedos From a high level, you either need to not dispose() your bloc (make it global and inherit it from the BlocProvider widget), or recreate it when returning to a screen.

@enricobenedos as @craiglabenz mentioned you should make sure you don't dispose your bloc if it's still needed. I would recommend disposing your bloc in the same widget that instantiated it. If after these explanations you're still having trouble, it'd be awesome if you could share a link to a sample app which demonstrates the issue you're running into.

Thanks!

thank you @craiglabenz for your explanation. Now I need to study how to use the BlocProvider and the first solution that I think to use is to doesn't dispose the blocs because in my case they are app wide. But this is totally wrong due to the @felangel suggestion (' I would recommend disposing your bloc in the same widget that instantiated it'). If I will find some time I will provide you a sample project.

@enricobenedos I don't believe these ideas are at odds if you create an app-wide bloc in your root widget :smile:

Only for show you this is my basic implementation of bloc pattern: MyProject. In this case I think it is correct to not dispose anything because my bloc need to be alive during all the app life.

@enricobenedos thanks for sharing. I noticed your project does not use the bloc or flutter_bloc library so I'm a bit confused. This repo is for issues related to the bloc/flutter_bloc/angular_bloc packages.

Anyway, I would recommend changing your MyApp widget to a StatefulWidget and creating/disposing the DogBloc there. Then you can use an InheritedWidget (in the flutter_bloc library it's called BlocProvider) to provide your bloc instance to the widgets in the sub-tree.

Oh! It's my mistake! I was sure I'm writing on Flutter framework repository 馃槄. Really sorry guys. Anyway I've updated my project with the dispose in the main.dart. Thank you for all the provided support to improve my code

Hi @felangel

I have an error too like : this issue. This is my BLoC code : https://pastebin.com/Tx24rGA4
and this is my detail_screen who implemented the BLoC : https://pastebin.com/DxTNwPpD

I hope u can help me for best practice using BLoC and solve this issue, Because if i deleted method dispose in detail screen, the apps is always working

@rrifafauzikomara for future reference please refrain from asking questions that aren't directly related to the bloc library here.

Regarding your question, you need to make sure you call dispose in the StatefulWidget where the Bloc is created. In your case you are creating the bloc instance inside of the bloc file which is wrong. Instead, you should create the bloc in initState of your detail screen and then you can uncomment the dispose and it should all work normally.

Okee, sorry about that @felangel . Because I search about my error then link to here. So I ask to you because u solve the problem. But in my case is different code and I hope you can help me for fixing my error hehe.

Btw, can u help me for giving me solution what must I edit? Because I read your instruction and still not understand what must I do for that.

@rrifafauzikomara you need to remove final bloc = MealsDetailBloc(); from your detail bloc and instead in your widget do something like:

final bloc = MealsDetailBloc();

@override
void initState() {
  super.initState();
  bloc.fetchDetailMeals(widget.idMeal);
}

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

This way your widget is creating and disposing the bloc. Before, the widget only disposes the bloc but it is never re-created when the widget is re-created. Hope that helps 馃憤

Oh man, thank you very much @felangel . It works very well on my application. You're like an expert in the field of BLoC. Btw can you write this answer to a question I made on StackOverflow? So that I can resolve the question and hopefully can help others.

Link: This

And I want to ask your opinion, by looking at the code I use BLoC is this good? Or need change? Because I saw the article that you created at: This you separated the event, state, and bloc.

Oi,

Em primeiro lugar, um 贸timo trabalho com a biblioteca!

Estou enfrentando o mesmo erro, mas a raz茫o pela qual acredito n茫o 茅 a estrutura, mas a vibra莽茫o em si. Ap贸s a rota da p谩gina Material, todas as p谩ginas da pilha s茫o recriadas. Problema para refer锚ncia # 14124 , # 11655 . N茫o sei o ETA para a corre莽茫o.

Os fluxos s茫o fechados corretamente na navega莽茫o quando onDispose 茅 chamado. Mas quando as p谩ginas na pilha s茫o reconstru铆das, esse problema 茅 produzido. Se voc锚 pode adicionar a verifica莽茫o para detectar a elimina莽茫o 茅 chamada antes de despachar o evento, esse erro pode ser evitado. Tentei adicionar cheques booleanos, mas isso n茫o ajudou.

Por favor, deixe-me saber se eu perdi alguma coisa.

https://github.com/flutter/flutter/pull/44731/

Hi,

Firstly, really great work with the library!

I am facing the same error but the reason i believe is not the framework but flutter itself. After Material page route, all the pages on the stack are re built. Issue for reference #14124 , #11655 . Dont know the ETA for the fix.

Streams are closed properly on navigation when on onDispose is called. But when pages on stack are rebuilt than this issue is produced. If you can add check to detect dispose is called before dispatching the event than this error can be avoided. I tried adding boolean checks but that did not help.

Please let me know if i missed something out.

I offered a pull to fix the problem with navigation rebuilding everything below materialApp.
https://github.com/flutter/flutter/pull/44731/

Alternatively I created a navigation package which besides not having any route reconstruction bugs, can be used with BLoC as it does not require context to navigate.

https://pub.dev/packages/get

@rrifafauzikomara you need to remove final bloc = MealsDetailBloc(); from your detail bloc and instead in your widget do something like:

final bloc = MealsDetailBloc();

@override
void initState() {
  super.initState();
  bloc.fetchDetailMeals(widget.idMeal);
}

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

This way your widget is creating and disposing the bloc. Before, the widget only disposes the bloc but it is never re-created when the widget is re-created. Hope that helps 馃憤

Hey @felangel this is still the case with the new close method? I should call it manually on dispose of my StatefulWidget?

An example might be:

@override
void initState() {
  super.initState();
  myBloc.fetchStuff();
}

@override
void dispose() {
  myBloc.close();
  super.dispose();
}

I'm getting a similar error and i'm not sure on how to handle it with v2.0.x

@nerder hey as a rule of thumb whoever creates the bloc should close the bloc. In general I would recommend leaving it up to BlocProvider to handle everything for you (so you don't need StatefulWidgets) but if you need to create a StatefulWidget and want to manually create an instance of the bloc then it should look something like:

class _MyWidgetState extends State<MyWidget> {
  final _bloc = MyBloc();

  @override
  void initState() {
    super.initState();
    _bloc.add(MyEvent());
  }

  @override
  Widget build(BuildContext context) { ... }

  @override
  void dispose() {
    _bloc.close();
    super.dispose();
  }
}

Hope that helps 馃憤

Hi, @felangel I know that I have commented about this in another git link.

but this error is a really big bang bang, and give us a lot of headaches.
This unpredictable when it will fire bad Cannot add new events after calling close. believe me that I have followed your best practice and all the solutions you have provided.

But I still get this error, it sometimes fires and sometimes doesn't.

I'm coming from web dev and used the Vuejs which use vuex as state management, and when I saw your library, it was the only thing that comes closer to vuex.

But the beaut of vuex, there's a global way to access any state and it's namespaced.

you don't need to pass as parameters the current instance to another page.

The only thing you do is just access the state via the namespace and dispatch or get data.
whenever you trigger the actions it will make sure to update all instances.

But with the block library, or flutter things are really mess or trick to get it right.
while trying to access or trigger some actions, and it's so stressful, when you cannot control or have a little an idea why things happen in that way.

So resuming maybe is a good thing about Cannot add new events after calling close but this is a big mess to deal with.

Sorry for the post, I just don't know how to explain my current problem as it has been explained with others, even though still getting same results.

This issue in most case scenarios happens with multiple pages using the same bloc.

Like you have the App bar that has notifications counter listener, you have other pages details that listen to the bloc, you have filter page that dispatch filter action to then update data loaded state.

All these things were supposed to work together, anyway.
Either way, I still love this library.

Hey @jjoao07 馃憢
Sorry to hear that! I'm happy to help you walk through your code and fix these issues. If you haven't already please join the bloc discord server and message me when you have some time. We can set up a live code share and work through the issues you're facing 馃憤

Check out JoseGeorges8/bloc_issue#1

I'd recommend having a bloc for each tab state along with AutomaticKeepAliveClientMixin to reduce the number of rebuilds.

Let me know if that helps!

@felangel thank you!!!! lol

I'm currently facing this issue, so apparently i'm closing the bloc early before I finish using them. But, not sure why bloc get's disposed if they are declared in MultiBloc Provider in main.dart, The time when this error shows up, is during a build in a child object. So my question is, how do I close them out of the child widgets?
````
runApp(MultiBlocProvider(
providers: [
BlocProvider(
create: (context) {
return AuthenticationBloc(
authenticationService: authenticationService)
..add(AppStarted());
},
child: App(authenticationService: authenticationService)),
BlocProvider(
create: (context) {
return NavigationBloc()..add(RegistrationStarted());
},
child: RegistrationView(),
),
BlocProvider(
create: (context) {
return RegistrationBloc()..add(RegistrationFormEvent());
},
child: UserDataView(),
)
],
child: App(authenticationService: authenticationService),
));
`````
I have it declared in the main application, which means I should close blocs when moving away to a different view out of those childs. so, how do I close them and where?

Hi @hackerunet you shouldn't need to close them manually; BlocProvider will handle disposing them when BlocProvider is disposed. Can you share a link to a sample app which illustrates the problem you're having? Thanks! 馃憤

I just find out what was going on, basically I was calling the dispose method manually and that was wrong because the one disposing the streams is the provider.
So removing dispose from my view fixed it.
I hope the resources get closed by the provider as you say because I don't have a clue how would I debug a memory leak caused by wrongly handled blocs.

@hackerunet as long as you are creating the bloc inside the create of BlocProvider it will be disposed automatically and you have nothing to worry about 馃槃

I just find out what was going on, basically I was calling the dispose method manually and that was wrong because the one disposing the streams is the provider.
So removing dispose from my view fixed it.
I hope the resources get closed by the provider as you say because I don't have a clue how would I debug a memory leak caused by wrongly handled blocs.

You can override the close() method in your bloc and print something when is called. Also, you can do additional clean-up there.

@override Future<void> close() async { super.close(); print('Closing Bloc'); // todo additional clean-up }

Hi @felangel ,

I experienced this issue as well on hot reload, and it was unexpected.
After some investigation I managed to reproduce it using flutter_bloc_with_stream example.

I modified the main.dart and added dialog_test.dart file.
Please let me know if this is a bug or just a misuse of bloc plugin.

The following error appears when displaying the dialog and pressing hot-reload:

Unhandled error Bad state: Cannot add new events after calling close occurred in bloc Instance of 'TickerBloc'.


main.dart

import 'package:flutter/material.dart';

import 'dialog_test.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Bloc with Streams'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showGeneralDialog(
            context: context,
            pageBuilder: (
              BuildContext buildContext,
              Animation<double> animation,
              Animation<double> secondaryAnimation,
            ) {
              return Builder(builder: (context) {
                return AlertDialog(
                  key: UniqueKey(), // IMPORTANT: without unique key the issue will not happen
                  content: DialogTest(),
                );
              });
            },
            barrierDismissible: false,
            barrierLabel:
                MaterialLocalizations.of(context).modalBarrierDismissLabel,
            barrierColor: Colors.black54,
            transitionDuration: const Duration(milliseconds: 500),
            transitionBuilder: _buildDialogTransitions,
          );
        },
        tooltip: 'Start',
        child: Icon(Icons.timer),
      ),
    );
  }

  Widget _buildDialogTransitions(
      BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child) {
    // IMPORTANT: without layout builder here it will not happen
    return LayoutBuilder(builder: (context, constraint) => child);
  }
}

dialog_test.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'bloc/ticker_bloc.dart';
import 'ticker/ticker.dart';

class DialogTest extends StatefulWidget {
  @override
  _DialogTestState createState() => _DialogTestState();
}

class _DialogTestState extends State<DialogTest> {
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider(create: (context) {
          var tickerBloc = TickerBloc(Ticker());
          tickerBloc.add(TickerStarted());

          return tickerBloc;
        })
      ],
      child: BlocBuilder<TickerBloc, TickerState>(
        builder: (context, state) {
          int count;
          if (state is TickerTickSuccess) {
            count = state.count;
          }

          return Container(
            margin: EdgeInsets.all(100),
            color: Colors.green,
            child:
                Center(child: Text(count == null ? 'Not started ' : '$count')),
          );
        },
      ),
    );
  }
}

I was pulling my hair all day due to this issue. I tried almost every solution but in the end, it was a stupid and unrelated mistake on my part.

For future reference, if anyone of you is using Injectable package for DI, make sure you don't mark any of your Blocs as a singleton. It will work the first time but break when you try to revisit that screen due to the DI forcing the widget to reuse the disposed bloc instance.

@xuhaibahmad you can mark your bloc as singleton as long as you make sure to reset the instance from get_it when closing the bloc. That will force get_it to create a new singleton instance when requested thereafter.

You can mix in this on your bloc and it will take care of it:

mixin AutoResetLazySingleton<E, S> on Bloc<E, S> {
  @override
  Future<void> close() {
    if (locator.isRegistered<Bloc<E, S>>(instance: this)) {
      locator.resetLazySingleton<Bloc<E, S>>(instance: this);
    }
    return super.close();
  }
}

Hello @felangel I'm facing the same issue, I have a profile page that uses ProfilBloc who's responsible for charging user and his posts. For the editing of user I'm using another bloc called EditBloc. I'm declaring an EditBloc in profile page and passing it trough parameter's while navigating to the EditPage. I also have a StreamSubscription who listens the states of EditBloc in my profile page for the purpose of refreshing user's data. The editing and the refresh are made but I'm getting this error after going back to ProfilPage and trying to remake a change.

Here is my profilePage :

class _ProfileState extends State<Profile> with TickerProviderStateMixin {
  ProfileBloc profilBloc;
  StreamSubscription editBlocSubscription;
  EditBloc editBloc = EditBloc();


@override
  void initState() {
    super.initState();

     editBlocSubscription = editBloc.listen((state){
      if(state is EditDone){
          setState(() {
          profilBloc.user = editBloc.getUser();

        });
      }
    });

Navigation to EditProfile :

Future pushToEditProfil(context) async{ Navigator.push((context), MaterialPageRoute( builder: (context) => EditProfileScreen(editBloc: editBloc, user: profilBloc.user,))); }

Here is my EditProfile :

class _ProfilEditState extends State<EditProfil> {

  EditBloc editBloc;
  User user;
  _ProfilEditState({
    @required this.editBloc,
    @required this.user,
  });
  @override
  void initState() {
    super.initState();
    editBloc = BlocProvider.of<EditBloc>(context);
    editBloc.user = user;
  }

  @override
  void dispose() {
    editBloc.close();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return BlocListener<EditBloc, EditState>(
        listener: (context, state) {
          if (state is EditDone) {
            setState(() {
              user = editBloc.user;
            });
          }
        },
        child: Scaffold(
          appBar: AppBar(
            backgroundColor: Colors.white,
            centerTitle: true,
            leading: IconButton(
                icon: Icon(Icons.arrow_back, color: Colors.black),
                onPressed: () => buildPop(context)),
            title: Text("Edit Profil",
                style: TextStyle(
                    color: Colors.black, fontWeight: FontWeight.bold)),
          ),
          body: WillPopScope(
            onWillPop: (){
              Navigator.pop(context);
            },
            child: buildColumn(context),
          )
        )
    );

  }

  Column buildColumn(BuildContext context ) {
    return Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Column(
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: CircleAvatar(
                    backgroundImage: NetworkImage(
                        'https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/27/e0/07/27e00727-f3c7-1c7d-4ccc-d5c8e9609bf6/source/512x512bb.jpg'),
                    radius: 40.0,
                  ),
                ),
                Text("Change photo")
              ],
            ),
            GestureDetector(
              onTap: () =>
                  Navigator.push(
                    (context),
                    MaterialPageRoute<EditName>(
                      builder: (_) =>
                          BlocProvider.value(
                            value: editBloc,
                            child: EditName(name: user.name),
                          ),
                    ),
                  ),
              child: _Container("Name", user.name),
            ),
            GestureDetector(
              onTap: () =>
                  Navigator.of(context).push(
                    MaterialPageRoute<EditUsername>(
                      builder: (_) =>
                          BlocProvider.value(
                            value: BlocProvider.of<EditBloc>(context),
                            child: EditUsername(userName: user.username),
                          ),
                    ),
                  ),
              child: _Container("Username", user.username),
            ),
            GestureDetector(
              onTap: () =>
                  Navigator.of(context).push(
                    MaterialPageRoute<EditBioScreen>(
                      builder: (_) =>
                          BlocProvider.value(
                            value: BlocProvider.of<EditBloc>(context),
                            child: EditBioScreen(),
                          ),
                    ),
                  ),
              child: _Container("Bio", user.bio),
            ),
          ],
        );
  }

and here is for example EditName page :

class EditName extends StatefulWidget {
  String name;

  EditName({Key key, @required this.name}) : super(key: key);
  @override
  State<StatefulWidget> createState() => _EditNameState(name);
}

class _EditNameState extends State<EditName> {
  _EditNameState(this.name);
  final String name;
  EditBloc editBloc;
  var editView = EditView(null, null, 1);

  @override
  void initState() {
    super.initState();
    editBloc = BlocProvider.of<EditBloc>(context);
    editView.info = editBloc.getUser().name;
    editView.maxLength = 50;
  }

  @override
  void dispose() {
    editBloc.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return BlocListener<EditBloc, EditState>(
      listener: (context, state) {
        if (state is EditDone) {

          Navigator.pop(context);
        }
      },
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.white,
          centerTitle: true,
          leading: FlatButton(
              child: (Text("X", style: TextStyle(color: Colors.black))),
              onPressed: () => Navigator.pop(context)),
          title: Text("Name",
              style:
                  TextStyle(color: Colors.black, fontWeight: FontWeight.bold)),
          actions: <Widget>[
            FlatButton(
              child: Text("Save"),
              onPressed: () => !editView.isValid ? null : editBloc.add(EditSaveName(editView.getInfo)),
            ),
          ],
        ),
        body: Container(
          padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0),
          child: Column(
            children: <Widget>[
              Align(
                alignment: Alignment.topLeft,
                child: Text("Name",
                    style: TextStyle(color: Colors.blueGrey, fontSize: 15)),
              ),
              editView,
            ],
          ),
        ),
      ),
    );

  }
}

Have you an idea of what I'm doing wrong? Thanks a lot!

Hello @av2ry-1030, at the moment I have no time to rewrite you the correct code. If you give me a link to clone your sample project I can help you.
Anyway there are some issue in your code, the first that it the same that cause this error is related to editBloc.close();. If EditBloc is created by ProfilePage you need to dispose your bloc in this page. You cannot call editBloc.close(); in EditProfile page if you need to use its state in ProfilePage.
Another strange thing is that you are telling us that you need to pass EditBloc to EditProfile page but you need to choose if you want to pass it through the constructor (I think it is not a good pattern using this library) or use the context to keep it.
In your sample your are mixing these two different approaches because first you require that editBloc is passed through constructor

_ProfilEditState({
    @required this.editBloc,
    @required this.user,
  });

but then you overwrite it using context to retrieve it

editBloc = BlocProvider.of<EditBloc>(context);

I think that reading docs and examples can really help you to better understand and study this library. You are very lucky because documentation is written very well!

Hi @av2ry-1030 馃憢

I suggest you upgrade to v5.0.0 of bloc library and your problem should be fixed. 馃憤

Hello @enricobenedos oh yes you're right. I tried so many things in order to find the problem that I wrote theses non-senses.. ^^'. Thank you.

I will first try to upgrade the library to v5.0 like @RollyPeres suggested and see what happens.

Hello @RollyPeres thank you for the suggestion, I tried to upgrade but it seem's that there is an problem of compatibility in the library.

The current Flutter SDK version is 1.12.13+hotfix.8.

Because flutter_bloc >=5.0.0 depends on flutter_cubit ^0.1.0 which depends on provider ^4.1.3, flutter_bloc >=5.0.0 requires provider ^4.1.3.
So, because provider >=4.1.0-dev+1 requires Flutter SDK version >=1.15.17-pre.5 and lit_app depends on flutter_bloc ^5.0.0, version solving failed.
pub get failed (1; So, because provider >=4.1.0-dev+1 requires Flutter SDK version >=1.15.17-pre.5 and lit_app depends on flutter_bloc ^5.0.0, version solving failed.)`

@av2ry-1030 looks like you need to upgrade Flutter. The version you're on is quite old (the current stable is at 1.17.4).

flutter upgrade
Was this page helpful?
0 / 5 - 0 ratings

Related issues

komapeb picture komapeb  路  3Comments

clicksocial picture clicksocial  路  3Comments

MahdiPishguy picture MahdiPishguy  路  3Comments

rsnider19 picture rsnider19  路  3Comments

ricktotec picture ricktotec  路  3Comments