Bloc: BlocProvider always Stack Overflow

Created on 17 Jun 2020  ยท  14Comments  ยท  Source: felangel/bloc

Describe the bug
When I follow the login with firebase tutorial in the flutterlibrary documentation, and I add the login feature to my application, the result is always a Stack Overflow error

To Reproduce
Steps to reproduce the behavior:

  1. Run App

Expected behavior
Can run normally

Screenshots
image

Logs

โ•โ•โ•โ•โ•โ•โ• Exception caught by widgets library โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
The following StackOverflowError was thrown building _InheritedProviderScope<LoginBloc>(value: null):
Stack Overflow

The relevant error-causing widget was
    BlocProvider<LoginBloc> 
lib/โ€ฆ/pages/login_page.dart:10
When the exception was thrown, this was the stack
#0      _HashMap.[]  (dart:collection-patch/collection_patch.dart:97:3)
#2      HttpOverrides.current  (dart:_http/overrides.dart:36:24)
#3      new HttpClient (dart:_http:1554:46)
#4      new IOClient 
package:http/src/io_client.dart:23
#5      createClient 
package:http/src/io_client.dart:16
...
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

My Code
login_bloc:

class LoginBloc extends Bloc<LoginEvent, LoginState> {

  final AuthRepository repository = AuthRepository();

  @override
  LoginState get initialState => LoginState.initial();

  @override
  Stream<Transition<LoginEvent, LoginState>> transformEvents(
    Stream<LoginEvent> events,
    TransitionFunction<LoginEvent, LoginState> transitionFn,
  ) {
    final nonDebounceStream = events.where((event) {
      return (event is! LoginEmailChanged && event is! LoginPasswordChanged);
    });
    final debounceStream = events.where((event) {
      return (event is LoginEmailChanged || event is LoginPasswordChanged);
    }).debounceTime(Duration(milliseconds: 300));
    return super.transformEvents(
      nonDebounceStream.mergeWith([debounceStream]),
      transitionFn,
    );
  }

  @override
  Stream<LoginState> mapEventToState(
    LoginEvent event,
  ) async* {
    if (event is LoginEmailChanged) {
      yield* _mapLoginEmailChangedToState(event.email);
    } else if (event is LoginPasswordChanged) {
      yield* _mapLoginPasswordChangedToState(event.password);
    } else if (event is GetSubmittedLogin) {
      yield* _mapSubmittedLoginToState(
        email: event.email,
        password: event.password,
      );
    }
  }

  Stream<LoginState> _mapLoginEmailChangedToState(String email) async* {
    yield state.update(
      isEmailValid: Validators.isValidEmail(email),
    );
  }

  Stream<LoginState> _mapLoginPasswordChangedToState(String password) async* {
    yield state.update(
      isPasswordValid: Validators.isValidSimplePassword(password),
    );
  }

  Stream<LoginState> _mapSubmittedLoginToState({
    String email,
    String password,
  }) async* {
    yield LoginState.loading();
    try {
      final loginModel = LoginModel(email: email, password: password);
      await repository.postLogin(loginModelToJson(loginModel));
      yield LoginState.success();
    } catch (_) {
      yield LoginState.failure();
    }
  }
}

login_state:

@immutable
class LoginState {
  final bool isEmailValid;
  final bool isPasswordValid;
  final bool isSubmitting;
  final bool isSuccess;
  final bool isFailure;

  bool get isFormValid => isEmailValid && isPasswordValid;

  LoginState({
    @required this.isEmailValid,
    @required this.isPasswordValid,
    @required this.isSubmitting,
    @required this.isSuccess,
    @required this.isFailure,
  });

  factory LoginState.initial() {
    return LoginState(
      isEmailValid: true,
      isPasswordValid: true,
      isSubmitting: false,
      isSuccess: false,
      isFailure: false,
    );
  }

  factory LoginState.loading() {
    return LoginState(
      isEmailValid: true,
      isPasswordValid: true,
      isSubmitting: true,
      isSuccess: false,
      isFailure: false,
    );
  }

  factory LoginState.failure() {
    return LoginState(
      isEmailValid: true,
      isPasswordValid: true,
      isSubmitting: false,
      isSuccess: false,
      isFailure: true,
    );
  }

  factory LoginState.success() {
    return LoginState(
      isEmailValid: true,
      isPasswordValid: true,
      isSubmitting: false,
      isSuccess: true,
      isFailure: false,
    );
  }

  LoginState update({
    bool isEmailValid,
    bool isPasswordValid,
  }) {
    return copyWith(
      isEmailValid: isEmailValid,
      isPasswordValid: isPasswordValid,
      isSubmitting: false,
      isSuccess: false,
      isFailure: false,
    );
  }

  LoginState copyWith({
    bool isEmailValid,
    bool isPasswordValid,
    bool isSubmitEnabled,
    bool isSubmitting,
    bool isSuccess,
    bool isFailure,
  }) {
    return LoginState(
      isEmailValid: isEmailValid ?? this.isEmailValid,
      isPasswordValid: isPasswordValid ?? this.isPasswordValid,
      isSubmitting: isSubmitting ?? this.isSubmitting,
      isSuccess: isSuccess ?? this.isSuccess,
      isFailure: isFailure ?? this.isFailure,
    );
  }

  @override
  String toString() {
    return '''LoginState {
      isEmailValid: $isEmailValid,
      isPasswordValid: $isPasswordValid,      
      isSubmitting: $isSubmitting,
      isSuccess: $isSuccess,
      isFailure: $isFailure,
    }''';
  }
}

login_event:

abstract class LoginEvent extends Equatable {
  const LoginEvent();

  @override
  List<Object> get props => [];
}

class LoginEmailChanged extends LoginEvent {
  final String email;

  const LoginEmailChanged({@required this.email});

  @override
  List<Object> get props => [email];

  @override
  String toString() => 'EmailChanged { email :$email }';
}

class LoginPasswordChanged extends LoginEvent {
  final String password;

  const LoginPasswordChanged({@required this.password});

  @override
  List<Object> get props => [password];

  @override
  String toString() => 'PasswordChanged { password: $password }';
}

class GetSubmittedLogin extends LoginEvent {
  final String email;
  final String password;

  const GetSubmittedLogin({
    @required this.email,
    @required this.password,
  });

  @override
  List<Object> get props => [email, password];

  @override
  String toString() {
    return 'SubmittedLogin { email: $email, password: $password }';
  }
}

login_page:

class LoginPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => LoginBloc(),
      child: LoginTemp(),
    );
  }
}

class LoginTemp extends StatefulWidget {
  @override
  _LoginTempState createState() => _LoginTempState();
}

class _LoginTempState extends State<LoginTemp> {

  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  LoginBloc _loginBloc;


  bool get isPopulated =>
      _emailController.text.isNotEmpty && _passwordController.text.isNotEmpty;

  bool isLoginButtonEnabled(LoginState state) {
    return state.isFormValid && isPopulated && !state.isSubmitting;
  }

  @override
  void initState() {
    super.initState();
    _loginBloc = BlocProvider.of<LoginBloc>(context);
    _emailController.addListener(_onEmailChanged);
    _passwordController.addListener(_onPasswordChanged);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(""),
      ),
      body: BlocListener<LoginBloc, LoginState>(
      listener: (context, state) {
        if (state.isFailure) {
          Scaffold.of(context)
            ..hideCurrentSnackBar()
            ..showSnackBar(
              SnackBar(
                content: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [Text('Login Failure'), Icon(Icons.error)],
                ),
                backgroundColor: Colors.red,
              ),
            );
        }
        if (state.isSubmitting) {
          Scaffold.of(context)
            ..hideCurrentSnackBar()
            ..showSnackBar(
              SnackBar(
                content: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text('Logging In...'),
                    CircularProgressIndicator(),
                  ],
                ),
              ),
            );
        }
        // if (state.isSuccess) {
        //   BlocProvider.of<AuthenticationBloc>(context)
        //       .add(AuthenticationLoggedIn());
        // }
      },
      child: BlocBuilder<LoginBloc, LoginState>(
        builder: (context, state) {
          return Padding(
            padding: EdgeInsets.all(20.0),
            child: Form(
              child: ListView(
                children: <Widget>[
                  // Padding(
                  //   padding: EdgeInsets.symmetric(vertical: 20),
                  //   child: Image.asset('assets/flutter_logo.png', height: 200),
                  // ),
                  TextFormField(
                    controller: _emailController,
                    decoration: InputDecoration(
                      icon: Icon(Icons.email),
                      labelText: 'Email',
                    ),
                    keyboardType: TextInputType.emailAddress,
                    autovalidate: true,
                    autocorrect: false,
                    validator: (_) {
                      return !state.isEmailValid ? 'Invalid Email' : null;
                    },
                  ),
                  TextFormField(
                    controller: _passwordController,
                    decoration: InputDecoration(
                      icon: Icon(Icons.lock),
                      labelText: 'Password',
                    ),
                    obscureText: true,
                    autovalidate: true,
                    autocorrect: false,
                    validator: (_) {
                      return !state.isPasswordValid ? 'Invalid Password' : null;
                    },
                  ),
                  Padding(
                    padding: EdgeInsets.symmetric(vertical: 20),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.stretch,
                      children: <Widget>[
                        RaisedButton(
                          child: Text('Login Bos'),
                          onPressed: isLoginButtonEnabled(state)
                              ? _onFormSubmitted
                              : null,
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){},
        tooltip: 'Increment',
        child: Icon(Icons.gps_fixed),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  void _onEmailChanged() {
    _loginBloc.add(
      LoginEmailChanged(email: _emailController.text),
    );
  }

  void _onPasswordChanged() {
    _loginBloc.add(
      LoginPasswordChanged(password: _passwordController.text),
    );
  }

  void _onFormSubmitted() {
    _loginBloc.add(
      GetSubmittedLogin(
        email: _emailController.text,
        password: _passwordController.text,
      ),
    );
  }
}

Please help me @felangel

question

Most helpful comment

Hi @RollyPeres

Already, because I was a little less thorough, Thank you for your attention

All 14 comments

Hi @wisnuwiry ๐Ÿ‘‹
Thanks for opening an issue!

Can you please provide a link to a sample app which illustrates the problem so that I can debug it locally? Thanks ๐Ÿ™

https://github.com/wisnuwiry/K9met9JKysdfk90 this is my sample repo project. Thanks

Please... @felangel

Hi @wisnuwiry ๐Ÿ‘‹

Have you managed to find the issue ?

Hi @RollyPeres

Already, because I was a little less thorough, Thank you for your attention

Is this fixed ? I have a very similar exception using Flutter web (dev channel) and bloc (^6.0.5).

The following JSRangeError was thrown building Builder:
Invalid argument: Maximum call stack size exceeded

The relevant error-causing widget was: 
  _InheritedProviderScope<StatementRepository> file:///Users/.../tools/flutter/.pub-cache/hosted/pub.dartlang.org/provider-4.3.2+2/lib/src/inherited_provider.dart:159:12
When the exception was thrown, this was the stack: 
packages/example/src/route/home/home_route.dart 35:25        set [_widget]
packages/example/src/route/home/home_route.dart 35:25        set [_widget]
packages/example/src/route/home/home_route.dart 35:25        set [_widget]
packages/example/src/route/home/home_route.dart 35:25        set [_widget]
packages/example/src/route/home/home_route.dart 35:25        set [_widget]
.

@GyuriMajercsik can you provide a repo sample?

@felangel , thanks for the quick response. Actually, it seems to be related to flutter dev branch or my code is broken somehow, but pretty hard to investigate. If I found that bloc is the reason I will come back with a smaller sample.

I found something similar: https://github.com/flutter/flutter/issues/66122

@GyuriMajercsik I have the same problem

I managed to reproduce it. The steps are as follows:

  • Run an application that uses bloc on the web
  • Run Hot Reload

The application only works again after running a clean flutter.

Flutter Doctor:

Doctor summary (to see all details, run flutter doctor -v):
[โœ“] Flutter (Channel dev, 1.23.0-7.0.pre, on Mac OS X 10.15.4 19E287 x86_64, locale pt-BR)

[โœ“] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[โœ“] Xcode - develop for iOS and macOS (Xcode 12.0.1)
[โœ“] Chrome - develop for the web
[โœ“] Android Studio (version 3.5)
[โœ“] VS Code (version 1.49.2)
[โœ“] Connected device (4 available)

โ€ข No issues found!

@ReniDelonzek can you provide a reproduction sample? Also can you confirm that the issue is bloc specific or can you also reproduce it with a regular provider? Thanks! ๐Ÿ™

Yes, I extracted a minimal piece of the application enough to reproduce the problem that is available here

I left all my dependencies on the original project as this can be useful.

The problem only occurs on the web

I managed to reproduce it. The steps are as follows:

  • Run an application that uses bloc on the web
  • Run Hot Reload

The application only works again after running a clean flutter.

Flutter Doctor:

Doctor summary (to see all details, run flutter doctor -v):
[โœ“] Flutter (Channel dev, 1.23.0-7.0.pre, on Mac OS X 10.15.4 19E287 x86_64, locale pt-BR)

[โœ“] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[โœ“] Xcode - develop for iOS and macOS (Xcode 12.0.1)
[โœ“] Chrome - develop for the web
[โœ“] Android Studio (version 3.5)
[โœ“] VS Code (version 1.49.2)
[โœ“] Connected device (4 available)

โ€ข No issues found!

I confirm that this happens for me as well.

Same problem. Think that this issue is not related to your package.

flutter clean help for me.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pczn0327 picture pczn0327  ยท  67Comments

bernaferrari picture bernaferrari  ยท  43Comments

konstantin-doncov picture konstantin-doncov  ยท  30Comments

anderscheow picture anderscheow  ยท  75Comments

felangel picture felangel  ยท  29Comments