3

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

StreamBuilder 快照.hasError

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();
  }
}
4

2 回答 2

2

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

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

根据Flutter Docs hasError 验证空值。

于 2019-09-06T20:09:22.977 回答
2

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

  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 类中抛出了一个错误,如果不是,是因为流构建器重建了小部件。这解决了我的问题。我不知道这是否是更好的解决方案,但目前解决了我的问题。

于 2019-01-04T14:05:14.213 回答