Bloc: Cannot add new events after calling close

Created on 14 Oct 2018  Β·  11Comments  Β·  Source: felangel/bloc

Hi,

I'm getting this error when trying to call a state method that dispatches an event:

I/flutter (27135): ══║ EXCEPTION CAUGHT BY GESTURE β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
I/flutter (27135): The following StateError was thrown while handling a gesture:
I/flutter (27135): Bad state: Cannot add new events after calling close
I/flutter (27135):
I/flutter (27135): When the exception was thrown, this was the stack:
I/flutter (27135): #1      Subject._add (package:rxdart/src/subjects/subject.dart:124:16)
I/flutter (27135): #2      Subject.add (package:rxdart/src/subjects/subject.dart:118:5)
I/flutter (27135): #3      _StreamSinkWrapper.add (package:rxdart/src/subjects/subject.dart:150:13)
I/flutter (27135): #4      Bloc.dispatch (package:bloc/src/bloc.dart:27:24)
I/flutter (27135): #5      AuthenticationBloc.onLoginButtonPressed (package:eat_app/blocs/authentication_bloc.dart:141:5)
I/flutter (27135): #6      _AuthPageState._onLoginButtonPressed (package:eat_app/pages/auth_page.dart:177:14)
I/flutter (27135): #7      _AuthPageState._buildLoginPage.<anonymous closure>.<anonymous closure> (package:eat_app/pages/auth_page.dart:157:29)
I/flutter (27135): #8      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:507:14)
I/flutter (27135): #9      _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:562:30)
I/flutter (27135): #10     GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24)
I/flutter (27135): #11     TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:242:9)
I/flutter (27135): #12     TapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:204:7)
I/flutter (27135): #13     GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:156:27)
I/flutter (27135): #14     _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:147:20)
I/flutter (27135): #15     _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:121:22)
I/flutter (27135): #16     _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:101:7)
I/flutter (27135): #17     _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:64:7)
I/flutter (27135): #18     _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:48:7)
I/flutter (27135): #19     _invoke1 (dart:ui/hooks.dart:153:13)
I/flutter (27135): #20     _dispatchPointerDataPacket (dart:ui/hooks.dart:107:5)
I/flutter (27135): (elided one frame from package dart:async)
I/flutter (27135):
I/flutter (27135): Handler: onTap
I/flutter (27135): Recognizer:
I/flutter (27135):   TapGestureRecognizer#5dc3e(debugOwner: GestureDetector, state: ready, won arena, finalPosition:
I/flutter (27135):   Offset(269.7, 428.6), sent tap down)
I/flutter (27135): ════════════════════════════════════════════════════════════════════════════════════════════════════

The function is being called here:

    authBloc.onLoginButtonPressed(
        email: _loginEmailTextEditingController.text,
        password: _loginPasswordTextEditingController.text);

And the function that calls the dispatch is:

void onLoginButtonPressed({@required String email, @required String password}) {
    dispatch(
      LoginButtonPressed(email: email, password: password )
    );
  }
question

All 11 comments

Hi @shnupta it sounds like the BlocBuilder has disposed the authBloc before an event was dispatched. Can you include details like does this happen on the first loginButtonPressed event or just on subsequent ones? Also, what happens after an event is dispatched?

It happens on every button press.

Here is how it is called from the log in button:

StandardFilledButton(
  // only allow the button to trigger the button press function is the state says it should
  // be enabled
  onPressed: authState.isAuthenticateButtonEnabled
      ? _onLoginButtonPressed
      : null,
  text: 'LOG IN',
  margin: EdgeInsets.only(
      left: 30.0, right: 30.0, top: 30.0, bottom: 10.0),
),

I don't think the event is actually dispatched. I think it fails when trying to add the event to the sink.

Can you please share the full build method of the widget?

Widget _buildLoginPage() {
    return BlocBuilder<AuthenticationState>(
      bloc: authBloc,
      builder: (BuildContext context, AuthenticationState authState) {
        if (authState.isAuthenticated) {
          // We have to wait for the widgets to build before the Navigator can change pages, else Flutter
          // gets angry.
          WidgetsBinding.instance.addPostFrameCallback((_) {
            Navigator.of(context).pushNamed('/home');
          });
        }

        // show a loading indicator if the state has updated to indicate it is processing a login
        if (authState.isLoading) {
          return Center(
            child: CircularProgressIndicator(),
          );
        }

        return Container(
          color: Colors.white,
          height: MediaQuery.of(context).size.height,
          width: MediaQuery.of(context).size.width,
          child: ListView(
            children: <Widget>[
              Container(
                padding: EdgeInsets.all(120.0),
                child: Center(
                  child: Icon(
                    Icons.fastfood,
                    color: Colors.redAccent,
                    size: 50.0,
                  ),
                ),
              ),
              NormalTextInput(
                title: 'EMAIL',
                hintText: 'Enter your email...',
                textEditingController: _loginEmailTextEditingController,
              ),
              Divider(
                height: 24.0,
              ),
              NormalTextInput(
                title: 'PASSWORD',
                hintText: 'Enter your password...',
                obscureText: true,
                textEditingController: _loginPasswordTextEditingController,
              ),
              Divider(
                height: 24.0,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.end,
                children: <Widget>[
                  FlatTextButton(
                    text: 'Forgot your password?',
                    onPressed: () => null,
                    padding: EdgeInsets.only(right: 20.0),
                  ),
                ],
              ),
              StandardFilledButton(
                // only allow the button to trigger the button press function is the state says it should
                // be enabled
                onPressed: authState.isAuthenticateButtonEnabled
                    ? _onLoginButtonPressed
                    : null,
                text: 'LOG IN',
                margin: EdgeInsets.only(
                    left: 30.0, right: 30.0, top: 30.0, bottom: 10.0),
              ),
            ],
          ),
        );
      },
    );
  }

Which is inside:

  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Container(
        height: MediaQuery.of(context)
            .size
            .height, // This queries the device to find out its screen information
        child: PageView(
          controller: _pageController,
          physics: BouncingScrollPhysics(),
          children: <Widget>[
            _buildLoginPage(),
            _buildAuthHomePage(), // Landing splash screen, app title and logo, login and signup buttons
            _buildSignupPage(),
          ],
          onPageChanged: (int page) {
            if (page == 1)
              FocusScope.of(context)
                  .requestFocus(FocusNode()); // hides the keyboard
          },
          scrollDirection: Axis.horizontal,
        ),
      ),
    );
  }

Okay that looks good. Can you show me how the authBloc is passed to the Login Page? Alternatively, if you can just share the full code with me so I can debug it locally it’d be great.

Yeah I was going to suggest that haha.

https://github.com/shnupta/eat_app

Haha awesome! Eating lunch now but I’ll take a look right after.

No worries, thanks! πŸ™‚

@shnupta I took a look at the code and the problem is the AuthPage Widget is rebuilt many times and since the LoginPage widget is not it's own stateful widget, it gets rebuilt as well. I would try to separate the LoginPage into it's own StatefulWidget with state for managing the text controllers as well as the authentication bloc. Let me know if that makes sense and/or if you need more guidance.

Thank you that worked! Would you be able to explain to me what causes the AuthPage widget to rebuild? Was it the text controllers?

I believe it's a combination of the Text Controllers and the Page View (https://github.com/flutter/flutter/issues/17328). Glad that solved your problem πŸ‘

Was this page helpful?
0 / 5 - 0 ratings