StreamBuilder snapshot.hasError 在键盘显示/隐藏颤动时显示多次

Posted

技术标签:

【中文标题】StreamBuilder snapshot.hasError 在键盘显示/隐藏颤动时显示多次【英文标题】:StreamBuilder snapshot.hasError show many times when keyboard show/hide flutter 【发布时间】:2019-05-30 07:28:07 【问题描述】:

我有一个登录屏幕,我正在使用 BloC 模式,但是当我单击按钮时验证失败,错误消息被多次调用,因为流构建器 snapshot.error 有一个值,我没有知道如何将其更改为仅在用户单击按钮并且验证实际上会引发错误时才显示错误。

class LoginPage extends StatefulWidget 
  static String tag = 'login-page';

  @override
  State<StatefulWidget> createState() => LoginState();


class LoginState extends State<LoginPage> 
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) 
    LoginBloc loginBloc = BlocProvider.of(context).loginBloc;

    return Scaffold(
      body: Container(
          width: MediaQuery.of(context).size.width,
          padding: EdgeInsets.all(16.0),
          decoration: BoxDecoration(
            gradient: LinearGradient(colors: [
              Colors.blueAccent,
              Colors.blue,
            ]),
          ),
          child: Center(
            child: Card(
              shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.all(Radius.circular(8.0))),
              elevation: 4.0,
              child: ListView(
                shrinkWrap: true,
                padding: EdgeInsets.only(left: 16.0, right: 16.0),
                children: <Widget>[
                  /*_logo(),*/
                  SizedBox(height: 24.0),
                  _emailField(loginBloc),
                  SizedBox(height: 8.0),
                  _passwordField(loginBloc),
                  SizedBox(height: 24.0),
                  _loginButtonSubmit(loginBloc),
                  _loading(loginBloc),
                  _error(loginBloc),
                  _success(loginBloc),
                  _settingsText()
                ],
              ),
            ),
          )),
    );
  

  Widget _logo() 
    return Hero(
      tag: 'hero',
      child: Padding(
        padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0),
        child: Center(
          child: Container(
            width: 100.0,
            height: 100.0,
            decoration: BoxDecoration(
              image: DecorationImage(
                fit: BoxFit.fill,
                image: AssetImage('assets/4.0x/ic_launcher.png'),
              ),
              borderRadius: BorderRadius.all(Radius.circular(50.0)),
            ),
          ),
        ),
      ),
    );
  

  Widget _emailField(LoginBloc loginBloc) 
    return StreamBuilder(
      stream: loginBloc.emailStream,
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) 
        //Anytime the builder sees new data in the emailStream, it will re-render the TextField widget
        return TextField(
          onChanged: loginBloc.setEmail,
          keyboardType: TextInputType.emailAddress,
          controller: _usernameController,
          decoration: InputDecoration(
            labelText: 'Usuário',
            errorText: snapshot
                .error, //retrieve the error message from the stream and display it
          ),
        );
      ,
    );
  

  Widget _passwordField(LoginBloc loginBloc) 
    return StreamBuilder(
      stream: loginBloc.passwordStream,
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) 
        return TextField(
          onChanged: loginBloc.setPassword,
          obscureText: true,
          controller: _passwordController,
          decoration: InputDecoration(
            labelText: 'Senha',
            errorText: snapshot.error,
          ),
        );
      ,
    );
  

  Widget _loginButtonSubmit(LoginBloc loginBloc) 
    return Padding(
      padding: EdgeInsets.symmetric(vertical: 16.0),
      child: RaisedButton(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(24),
        ),
        onPressed: () 
          loginBloc.submit(
              LoginRequest(_usernameController.text, _passwordController.text));
        ,
        padding: EdgeInsets.all(12),
        color: Colors.blue,
        child: Text('Entrar', style: TextStyle(color: Colors.white)),
      ),
    );
  

  Widget _loading(LoginBloc loginBloc) 
    return StreamBuilder(
        stream: loginBloc.loadingStream,
        initialData: false,
        builder: (BuildContext context, AsyncSnapshot<bool> snapshot) 
          return Center(
            child: snapshot.data
                ? Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: CircularProgressIndicator(),
                  )
                : null,
          );
        );
  

  Widget _error(LoginBloc loginBloc) 
    return StreamBuilder(
        stream: loginBloc.successStream,
        builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) 
          if (snapshot.hasError) 
            _onWidgetDidBuild(() 
              Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('$snapshot.error'),
                backgroundColor: Colors.red,
              ));
            );
          
          return Container();
        );
  

  Widget _success(LoginBloc loginBloc) 
    return StreamBuilder(
        stream: loginBloc.successStream,
        initialData: null,
        builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) 
          if (snapshot.hasData && snapshot.data.erro == 0) 
            _onWidgetDidBuild(() 
              Navigator.of(context).pushReplacement(
                  MaterialPageRoute(builder: (context) => HomePage()));
            );
          
          return Container();
        );
  

  Widget _settingsText() 
    return Center(
      child: GestureDetector(
        onTap: () 
          Navigator.of(context).push(
              MaterialPageRoute(builder: (context) => LoginSettingsPage()));
        ,
        child: Padding(
          padding: EdgeInsets.fromLTRB(16.0, 0, 16.0, 16.0),
          child: Text(
            "Configurações",
            style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
          ),
        ),
      ),
    );
  

  void _onWidgetDidBuild(Function callback) 
    WidgetsBinding.instance.addPostFrameCallback((_) 
      callback();
    );
  

集团

class LoginBloc with Validator 
  //RxDart's implementation of StreamController. Broadcast stream by default
  final _emailController = BehaviorSubject<String>();
  final _passwordController = BehaviorSubject<String>();
  final _loadingController = BehaviorSubject<bool>();
  final _successController = BehaviorSubject<LoginResponse>();
  final _submitController = PublishSubject<LoginRequest>();

  //Return the transformed stream
  Stream<String> get emailStream => _emailController.stream.transform(performEmptyEmailValidation);
  Stream<String> get passwordStream => _passwordController.stream.transform(performEmptyPasswordValidation);
  Stream<bool> get loadingStream => _loadingController.stream;
  Stream<LoginResponse> get successStream => _successController.stream;

  //Add data to the stream
  Function(String) get setEmail => _emailController.sink.add;
  Function(String) get setPassword => _passwordController.sink.add;
  Function(LoginRequest) get submit => _submitController.sink.add;

  LoginBloc() 
    _submitController.stream.distinct().listen((request) 
      _login(request.username, request.password);
    );
  

  _login(String useName, String password) async 
    _loadingController.add(true);

    ApiService.login(useName, password).then((response) 
      if (response.erro == 0) 
        saveResponse(response);
       else 
        final error = Utf8Codec().decode(base64.decode(response.mensagem));
        _successController.addError(error);
        print(error);
      
      _loadingController.add(false);
    ).catchError((error) 
      print(error);
      _loadingController.add(false);
      _successController.addError("Falha ao realizar login!");
    );
  

  saveResponse(LoginResponse response) 
    SharedPreferences.getInstance().then((preferences) async 
      var urlSaved = await preferences.setString(
          Constants.LOGIN_RESPONSE, response.toJson().toString());
      if (urlSaved) 
        _successController.add(response);
      
    ).catchError((error) 
      _successController.addError(error);
    );
  

  dispose() 
    _emailController.close();
    _passwordController.close();
    _loadingController.close();
    _successController.close();
    _submitController.close();
  

【问题讨论】:

【参考方案1】:

我在单击 InputField 并更改焦点时找到了错误的解决方案,StreamBuilder 重建 de 小部件每次都会再次显示错误。 我只是在开始显示错误之前进行验证以考虑快照的状态。

  Widget _error(LoginBloc loginBloc) 
    return StreamBuilder(
        stream: loginBloc.successStream,
        builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) 
          if (snapshot.connectionState == ConnectionState.active &&
              snapshot.hasError) 
            _onWidgetDidBuild(() 
              Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('$snapshot.error'),
                backgroundColor: Colors.red,
              ));
            );
          
          return Container();
        );
  

如果是活动的,是因为我在 Bloc 类中抛出了一个错误,如果不是,是因为流构建器重建了小部件。这解决了我的问题。 我不知道这是否是更好的解决方案,但目前解决了我的问题。

【讨论】:

【参考方案2】:

我遇到了同样的问题,添加 initialdata null 对我来说很好。

return new StreamBuilder(
          stream: _bloc.stream,
          initialData: null,
          builder: (BuildContext context, AsyncSnapshot snapshot) 
            if (snapshot.hasError) 

根据Flutter Docs hasError 验证空值。

【讨论】:

以上是关于StreamBuilder snapshot.hasError 在键盘显示/隐藏颤动时显示多次的主要内容,如果未能解决你的问题,请参考以下文章

FirebaseStorage + Flutter,streamBuilder?

如何使用 StreamBuilder 更新 TextField 的值?

Flutter:Streambuilder - 关闭流

Flutter 中 StreamBuilder 和流的问题(接收重复数据)

为啥 StreamBuilder 中没有刷新数据?

StreamBuilder 限制