如何在 Flutter App 中正确使用 BlocListener 和 BlocProvider

Posted

技术标签:

【中文标题】如何在 Flutter App 中正确使用 BlocListener 和 BlocProvider【英文标题】:How to correctly use BlocListener and BlocProvider in Flutter App 【发布时间】:2020-10-01 08:18:34 【问题描述】:

我在我的 Flutter 应用程序中使用了 flutter_bloc 4.0.0,我使用了 Felix Angelov (https://medium.com/flutter-community/firebase-login-with-flutter-bloc-47455e6047b0) 的示例来实现使用 bloc 模式的登录或登录流程。它工作正常,但在我更新了我的 Flutter 并稍后检查了我的代码后,我发现了一系列错误。我不明白他们为什么要来,因为上周一切都很好。突然间,小部件的 build 方法中的 bloc 实现对我来说是错误的。我收到错误消息:

1."‘BlocListener的值类型不能从方法build中返回,因为它的返回类型是widget"

    “BlocProvider 的值类型> 无法从方法构建中返回,因为它的返回类型为小部件”

第一个错误的代码

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),
    ),
  );

【问题讨论】:

【参考方案1】:

也许我的回答对你来说有点过时了,但我希望它对其他人有所帮助。

首先,BlocBuilder/BlocListener 应该在对应的 BlocProvider 范围内。

您应该在无状态/有状态小部件的构建方法中返回 BlocBuilder。如果你想结合 BlocListener 和 BlocBuilder 你可以使用 BlocConsumer 小部件。此外,您可以添加参数 buildWhen 来指定 BlocBuilder 是否应根据传入状态重建您的小部件。这是一个例子:

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);
        
      ,
    );
  

所以,我们的 Screen Widget 应该在 ScreenBloc 的范围内(作为它的子项)。我们可以通过以下方式实现:

BlocProvider<ScreenBloc>(
  create: (context) => ScreenBloc(),
  child: Screen(),
);

【讨论】:

以上是关于如何在 Flutter App 中正确使用 BlocListener 和 BlocProvider的主要内容,如果未能解决你的问题,请参考以下文章

Flutter app:如何实现正确的注销功能?

Flutter 系列三:优化"书架"App,以正确的方式管理数据!

Flutter 状态管理 | StreamBuild 局部刷新的效果Flutter局部刷新

Flutter中如何在PageView中正确使用GoogleMap

如何显示编号用户可以输入的字符数以及用户在 TextFormField Flutter 中输入的字符数

哪个是我的 Flutter 应用程序的正确包名称?