Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly use BlocListener and BlocProvider in Flutter App

I am using flutter_bloc 4.0.0 in my Flutter App, I used the example from Felix Angelov (https://medium.com/flutter-community/firebase-login-with-flutter-bloc-47455e6047b0) on implementing a sign in or login flow using the bloc pattern. It was working fine but after I updated my Flutter and checked on my code later I have found a series of errors. I don't understand why they are coming because last week everything was just fine. The implementation of the bloc in the build method for a widget has become wrong for me all of a sudden. I am getting the error:

1."‘A value type of BlocListener can’t be returned from the method build because it has a return type of widget"

  1. ‘A value type of BlocProvider> can’t be returned from the method build because it has a return type of widget’

the code for the first error

class LoginForm extends StatefulWidget {
  final UserRepository _userRepository;

  LoginForm({Key key, @required UserRepository userRepository})
      : assert(userRepository != null),
        _userRepository = userRepository,
        super(key: key);

  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  LoginBloc _loginBloc;

  UserRepository get _userRepository => widget._userRepository;

  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 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(LoggedIn());
        }
      },
      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),
                  ),
                  Container(
                    margin: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 20.0),
                    height: 45.0,
                    child: TextFormField(
                      controller: _emailController,
                      style: TextStyle(
                        fontFamily: 'Avenir-Medium',
                        fontSize: 12.0,
                        color: Colors.black,
                      ),
                      decoration: InputDecoration(
                        border: OutlineInputBorder(
                            borderRadius: const BorderRadius.all(
                              const Radius.circular(7.0),
                            ),
                            borderSide: BorderSide(
                              color: Colors.grey[200],
                              width: 7.0,
                            )),
                        labelText: 'email',
                      ),
                      keyboardType: TextInputType.emailAddress,
                      autovalidate: true,
                      autocorrect: false,
                      validator: (_) {
                        return !state.isEmailValid ? 'Invalid Email' : null;
                      },
                    ),
                  ),
                  Container(
                    height: 45.0,
                    child: TextFormField(
                      style: TextStyle(
                        fontFamily: 'Avenir-Medium',
                        fontSize: 12.0,
                        color: Colors.black,
                      ),
                      controller: _passwordController,
                      decoration: InputDecoration(
                        border: OutlineInputBorder(
                            borderRadius: const BorderRadius.all(
                              const Radius.circular(7.0),
                            ),
                            borderSide: BorderSide(
                              color: Colors.grey[200],
                              width: 0.0,
                            )),
                        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>[
                        LoginButton(
                          onPressed: _onFormSubmitted,

//                          isLoginButtonEnabled(state)
//                              ? _onFormSubmitted
//                              : null,
                        ),
                        GoogleLoginButton(),
                        AppleSignInButton(),
                        CreateAccountButton(userRepository: _userRepository),
                        ForgotPasswordButton()
                      ],
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }

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

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

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

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

the code for the second error above is as follows

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  BlocSupervisor.delegate = SimpleBlocDelegate();
  final UserRepository userRepository = UserRepository();
  runApp(
    BlocProvider(
      create: (context) => AuthenticationBloc(
        userRepository: userRepository,
      )..add(AppStarted()),
      child: App(userRepository: userRepository),
    ),
  );
}

image for the error

the second error

like image 994
K.chim Avatar asked Jun 11 '20 16:06

K.chim


1 Answers

Maybe my answer is a bit outdated for you, but I hope it will help others.

First of all, BlocBuilder/BlocListener should be in the scope of a corresponding BlocProvider.

You should return BlocBuilder in the build method of your Stateless/Stateful widget. If you want to combine BlocListener and BlocBuilder you can use BlocConsumer widget. In addition, you can add parameter buildWhen to specify if BlocBuilder should rebuild your widget depending on incoming state. Here's an example:

class __ScreenWidgetState extends State<Screen> {

  @override
  Widget build(BuildContext context) {
    return BlocConsumer<ScreenBloc, ScreenState>(
      buildWhen: (previousState, state) {
        return state is! DontBuild;
      },
      builder: (BuildContext context, state) {
        return Text(state.text);
      },
      listener: (BuildContext context, state) {
        if (state is ShowFlushbar) {
          showFlushBar(context: context, message: state.text);
        }
      },
    );
  }
}

So, our Screen Widget should be in the Scope of the ScreenBloc (as a child of it). We can achieve this in the following way:

BlocProvider<ScreenBloc>(
  create: (context) => ScreenBloc(),
  child: Screen(),
);
like image 138
Thomas Avatar answered Sep 20 '22 20:09

Thomas