flutter bloc pattern navigation back results in bad state
Asked Answered
A

2

7

I have a problem/question regarding the bloc plattern with flutter. Currently, i am starting my app like this

class _MyAppState extends State<MyApp> {

@override
  Widget build(BuildContext context) {        
    return BlocProvider(
        bloc: MyBloc(),
        child: MaterialApp(
          title: "MyApp",
          home: MyHomePage(),    
          routes: {
            '/homePage': (context) => MyHomePage(),
            '/otherPage': (context) => OtherPage(),
            '/otherPage2': (context) => OtherPage2(),
            ...
          },
        ));

So that i can retrieve/access myBloc like

myBloc = BlocProvider.of(context) as MyBloc;

and the data represented by the state like

BlocBuilder<MyBlocEvent, MyObject>(
    bloc: myBloc,
    builder: (BuildContext context, MyObject myObject) {         
     ....
     var t = myObject.data;
     ....
     myBloc.onFirstEvent();
     ...
 }; 

wherever i need it.

MyBloc is implemented like this:

abstract clas MyBlocEvent {}
class FirstEvent extends MyBlocEvent {}
class SecondEvent extends MyBlocEvent {}

class MyBloc extends Bloc<MyBlocEvent , MyObject>

void onFirstEvent()
{
  dispatch(FirstEvent());
}

void onSecondEvent()
{
  dispatch(SecondEvent());
}

@override
Stream<MyObject> mapEventToState( MyObject state, MyBlocEvent event) async* {
if (event is FirstEvent) {
    state.data = "test1";
}
else if (event is SecondEvent) {
    state.otherData = 5;
}
    yield state;
}

The problem i now have, is that as soon as i change on of the state values and call

Navigator.pop(context)

to go back in the current stack, i can't change anything is the state anymore because the underlying stream seems to be closed. It fails with the message:

Another exception was thrown: Bad state: Cannot add new events after calling close"

Now this only happens after i call pop. If i only push new screens i can happily change the state data without any problems.

Am i doing something wrong regarding the Navigation here or is there something else i didn't catch regarding flutter or the bloc pattern itself?

Academicism answered 24/10, 2018 at 5:29 Comment(1)
I believe it is the BlocProvider used, I just switched to pub.dartlang.org/packages/provider and this no longer happens to me... We'll see though. Would like a better explanation though.Gladi
B
7

Bad state: Cannot add new events after calling close

This error means that you are calling add on a StreamController after having called close:

 var controller = StreamController<int>();
 controller.close();
 controller.add(42); // Bad state: Cannot add new events after calling close

It is likely related to you calling close inside the dispose method the "wrong" widget.

A good rule of thumb is to never dispose/close an object outside of the widget that created it. This ensure that you cannot use an object already disposed of.

Broderickbrodeur answered 11/3, 2019 at 13:11 Comment(1)
To fix it, I had to remove the dispose methodYippee
V
2

Hope this helps in your debugging.

The navigation of the app depends on your widget designs.

I use stateless widgets and render the view using bloc's data.

Whenever i navigate to another page, i would pop the current widget and navigate to the next widget.

The next stateless widget declare the bloc, then in your subsequent stateless widgets should contain calls like MyBloc.dispatch(event(param1: value1, param2: value2));

In MyBloc, you need to set the factory of your state that contains final values;

@override
Stream<MyObject> mapEventToState( MyObject state, MyBlocEvent event) async* {
if (event is FirstEvent) {
    // set it in the state, so this code is omitted
    // state.data = "test1";
    // add this
    yield state.sampleState([], "test1");
}
else if (event is SecondEvent) {
    // state.otherData = 5;
    yield state.sampleState([], 5);
} else {
    yield state.sampleState([], null);
}

The MyObjectState needs to be setup like this,

class MyObjectState {
  final List<Bar> bars;
  final String Foo;

  const MyObjectState(
      {this.bars,
      this.foo,
  });

  factory MyObjectState.sampleState(List<Bar> barList, String value1) {
    return MyObjectState(bars: barList, foo: message);
  }
}

So that the stateless widget can use the bloc like this MyBloc.currentState.sampleState.foo

You can try run Felix Angelov's flutter project. Login Flow Example

Ventriculus answered 8/1, 2019 at 4:59 Comment(1)
Thanks for your answer on such an old ticket, didn't expect to get any response at this point :) I use the navigator to keep history of the visited pages, so that i don't have to implement all that myself, so just popping the current page and pushing the next would require me to do so. The example form Felix Angelov is quite simple (like many that i found, sadly) and also won't provide the history. I will try your suggestions in the next project i'll use the bloc pattern in.Academicism

© 2022 - 2024 — McMap. All rights reserved.