如何在颤动中使用 Bloc 模式处理用户注册错误

Posted

技术标签:

【中文标题】如何在颤动中使用 Bloc 模式处理用户注册错误【英文标题】:How to handle user registration error using Bloc pattern in flutter 【发布时间】:2020-01-17 21:55:02 【问题描述】: 这是我尝试过的一些代码。在这段代码中,我使用了 bloc。但我想在此注册页面中进行错误处理。 用户注册时如果注册成功则只需要打开OTP对话框 当用户注册失败时,用户会收到相应的消息。 如果正在注册,则应指示加载。 成功注册后。它将显示一个 OTP。输入 otp 后,它将重定向另一个警报对话框。希望你理解这个问题,请帮助我。我也在这段代码中尝试了很多次错误处理,您发现错误处理但它不正确。我希望你能帮助我。你的一点帮助就能让我开心。

这里是完整的源代码https://github.com/rutvikgumasana/signup/tree/master

.

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_masked_text/flutter_masked_text.dart';

import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:passcode/passcode.dart';
import 'package:pin_code_fields/pin_code_fields.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tudo/src/modules/signup/_models/countries.dart';
import 'package:tudo/src/modules/signup/index.dart';
import 'package:tudo/src/styles/colors.dart';

import 'package:tudo/src/utils/app_constants_value.dart';
import 'package:tudo/src/utils/navigation_helper.dart';
import 'package:tudo/src/utils/roundrectbutton.dart';
import 'package:tudo/src/utils/validator.dart';
import 'package:tudo/src/widgets/loader.dart';

import 'package:tudo/src/widgets/toast.dart';
import 'dart:math' as math;

class SignupScreen extends StatefulWidget 
  const SignupScreen(
    Key key,
    @required SignupBloc signupBloc,
  )  : _signupBloc = signupBloc,
        super(key: key);

  final SignupBloc _signupBloc;

  @override
  SignupScreenState createState() 
    return new SignupScreenState(_signupBloc);
  


class SignupScreenState extends State<SignupScreen>
    with TickerProviderStateMixin 
  String submittedString = "";
  final changeNotifier = StreamController<Functions>.broadcast();

  final SignupBloc _signupBloc;
  SignupScreenState(this._signupBloc);
  final formKey = new GlobalKey<FormState>();
  bool _validate = false;
  List<Country> _countries = [];
  bool _isError = false;
  bool _isLoading = false;
  bool _obscureText = true;
  Person person = new Person();
  var controller = new MaskedTextController(mask: '(000)-000-0000');

  String passcode;
  final _emailFocusNode = new FocusNode();
  final _passwordFocusNode = new FocusNode();
  final _fnameFocuNode = new FocusNode();
  final _lnameFocusNode = new FocusNode();
  final TextEditingController _email = new TextEditingController();
  final TextEditingController _add = new TextEditingController();
  final TextEditingController _fn = new TextEditingController();
  final TextEditingController _ln = new TextEditingController();
  final TextEditingController _pho = new TextEditingController();
  final TextEditingController _pass = new TextEditingController();

  TextEditingController phoneController = new TextEditingController();

  static List<CountryModel> _dropdownItems = new List();

  String otpWaitTimeLabel = "";

  CountryModel _dropdownValue;
  TextEditingController otpcontroller = TextEditingController();
  String thisText = "";
  int pinLength = 6;

  bool hasError = false;
  bool showAlertBox = false;
  String errorMessage;

  SharedPreferences prefs;
  DateTime target;
  String timeLeft = "";
  bool running = true;

  @override
  void initState() 
    super.initState();

    this._signupBloc.dispatch(LoadSignupEvent());
  

  @override
  void dispose() 
    prefs.setInt('target', target.millisecondsSinceEpoch);
    running = false;
    super.dispose();
  

  void _toggle() 
    setState(() 
      _obscureText = !_obscureText;
    );
  

  Widget _buildLogo() 
    return new Image(
      image: new AssetImage("assets/logo.png"),
      height: 150,
      width: 150,
    );
  

  Widget _buildEmailField() 
    return TextFormField(
      controller: _email,
      focusNode: _emailFocusNode,
      decoration: new InputDecoration(
        prefixIcon: Icon(
          Icons.email,
        ),
        labelText: AppConstantsValue.appConst['signup']['email']['translation'],
        border: UnderlineInputBorder(),
        filled: false,
        hintText: 'Your email address',
      ),
      keyboardType: TextInputType.emailAddress,
      validator: Validators().validateEmail,
      onSaved: (String value) 
        person.email = value;
      ,
    );
  

  Widget _buildCountry(List<Country> countries) 
    if (countries.length > 0 && _dropdownItems.length != countries.length - 1) 
      print("countries list");
      print(countries[0].name);
      for (int i = 0; i < countries.length; i++) 
        if (countries[i].name.toLowerCase() != 'world') 
          _dropdownItems.add(
            CountryModel(
                country: countries[i].name, countryCode: countries[i].isdCode),
          );
        
      
    
    return FormBuilder(
      autovalidate: true,
      initialValue: ,
      child: FormBuilderCustomField(
        attribute: "Country",
        validators: [
          FormBuilderValidators.required(),
        ],
        formField: FormField(
          builder: (FormFieldState<dynamic> field) 
            return DropdownButtonHideUnderline(
              child: new Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: <Widget>[
                  new InputDecorator(
                    decoration: InputDecoration(
                      filled: false,
                      hintText: 'Choose Country',
                      prefixIcon: Icon(Icons.location_on),
                      labelText: _dropdownValue == null
                          ? 'Where are you from'
                          : 'From',
                      errorText: field.errorText,
                    ),
                    isEmpty: _dropdownValue == null,
                    child: new DropdownButton<CountryModel>(
                      value: _dropdownValue,
                      isDense: true,
                      onChanged: (CountryModel newValue) 
                        print('value change');
                        print(newValue);
                        person.country = newValue.country;
                        person.countryCode = newValue.countryCode;
                        setState(() 
                          _dropdownValue = newValue;
                          phoneController.text = _dropdownValue.countryCode;
                          field.didChange(newValue);
                        );
                      ,
                      items: _dropdownItems.map(
                        (CountryModel value) 
                          return DropdownMenuItem<CountryModel>(
                            value: value,
                            child: Text(value.country),
                          );
                        ,
                      ).toList(),
                    ),
                  ),
                ],
              ),
            );
          ,
        ),
      ),
    );
  

  Widget _buildPhonefiled() 
    return Row(
      children: <Widget>[
        new Expanded(
          child: new TextFormField(
            controller: phoneController,
            enabled: false,
            decoration: InputDecoration(
              filled: false,
              prefixIcon: Icon(FontAwesomeIcons.globe),
              labelText: 'code',
              hintText: "Country code",
            ),
          ),
          flex: 2,
        ),
        new SizedBox(
          width: 10.0,
        ),
        new Expanded(
          child: new TextFormField(
            controller: controller,
            keyboardType: TextInputType.number,
            validator: Validators().validateMobile,
            decoration: InputDecoration(
              filled: false,
              labelText: 'mobile',
              hintText: "Mobile number",
              prefixIcon: new Icon(Icons.mobile_screen_share),
            ),
            onSaved: (String value) 
              person.phoneNumber = value;
            ,
          ),
          flex: 5,
        ),
      ],
    );
  

  Widget _buildFnamefiled() 
    return TextFormField(
      controller: _fn,
      focusNode: _fnameFocuNode,
      decoration: new InputDecoration(
          filled: false,
          hintText: 'Enter your First name',
          prefixIcon: Icon(
            Icons.account_circle,
            //  size: 28.0,
          ),
          labelText: AppConstantsValue.appConst['signup']['firstname']
              ['translation']),
      keyboardType: TextInputType.text,
      validator: Validators().validateName,
      onSaved: (String value) 
        person.firstname = value;
      ,
    );
  

  Widget _buildLnamefiled() 
    return TextFormField(
      validator: Validators().validateName,
      controller: _ln,
      focusNode: _lnameFocusNode,
      decoration: new InputDecoration(
          filled: false,
          hintText: 'Enter your Last name',
          prefixIcon: Icon(
            Icons.account_circle,
            //  size: 28.0,
          ),
          labelText: AppConstantsValue.appConst['signup']['lastname']
              ['translation']),
      keyboardType: TextInputType.text,
      onSaved: (String value) 
        person.lastname = value;
      ,
    );
  

  Widget _buildPasswordfiled() 
    return TextFormField(
      validator: Validators().validatePassword,
      obscureText: _obscureText,
      controller: _pass,
      focusNode: _passwordFocusNode,
      decoration: new InputDecoration(
        filled: false,
        hintText: 'Enter your password',
        prefixIcon: Icon(
          Icons.***_key,
        ),
        suffixIcon: GestureDetector(
          dragStartBehavior: DragStartBehavior.down,
          onTap: _toggle,
          child: Icon(
            _obscureText ? Icons.visibility : Icons.visibility_off,
            semanticLabel: _obscureText
                ? AppConstantsValue.appConst['login']['show_password']
                    ['translation']
                : AppConstantsValue.appConst['login']['hide_password']
                    ['translation'],
          ),
        ),
        labelText: AppConstantsValue.appConst['signup']['password']
            ['translation'],
      ),
      onSaved: (String value) 
        person.password = value;
      ,
    );
  

  Widget _buildTermsAndContionsCheck() 
    return FormField(
      builder: (FormFieldState state) 
        return Row(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            Checkbox(
              value: person.termsAndCondition,
              onChanged: (bool value) 
                setState(() 
                  person.termsAndCondition = value;
                );
              ,
            ),
            Text(
              AppConstantsValue.appConst['signup']['termsandcondition']
                  ['translation'],
            ),
          ],
        );
      ,
    );
  

  _onAlertotp() 
    return showDialog<void>(
      context: context,
      barrierDismissible: false, // user must tap button!
      builder: (BuildContext context) 
        return AlertDialog(
          title: Text('Enter OTP'),
          content: SingleChildScrollView(
            child: ListBody(
              children: <Widget>[
                Container(
                  height: MediaQuery.of(context).size.height / 2.4,
                  width: MediaQuery.of(context).size.width,
                  alignment: Alignment.center,
                  child: ListView(
                    children: <Widget>[
                      Padding(
                        padding: const EdgeInsets.symmetric(vertical: 8.0),
                        child: Text(
                          'We have Texted and/or Emailed OTP (One Time Pin) to your registered cell phone and/ or email account. Please check and enter OTP below to activate your TUDO account.',
                          style: TextStyle(
                              fontWeight: FontWeight.bold, fontSize: 15),
                          textAlign: TextAlign.center,
                        ),
                      ),
                      Center(
                        child: Text(
                          "timerString",
                          style: TextStyle(fontSize: 25),
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.symmetric(
                            vertical: 8.0, horizontal: 30),
                        child: PinCodeTextField(
                          length: 6, // must be greater than 0
                          obsecureText: false, //optional, default is false
                          shape: PinCodeFieldShape
                              .underline, //optional, default is underline
                          onDone: (String value) 
                            setState(() 
                              passcode = value;
                              print(value);
                            );
                          ,

                          textStyle: TextStyle(
                              fontWeight: FontWeight
                                  .bold), //optinal, default is TextStyle(fontSize: 18, color: Colors.black, fontWeight: FontWeight.bold)
                          onErrorCheck: (bool value) 
                            setState(() 
                              hasError = value;
                            );
                          ,
                          shouldTriggerFucntions:
                              changeNotifier.stream.asBroadcastStream(),
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.symmetric(horizontal: 30.0),
                        child: Text(
                          hasError
                              ? "*Please fill up all the cells and press VERIFY again"
                              : "",
                          style: TextStyle(
                              color: Colors.red.shade300, fontSize: 12),
                        ),
                      ),
                      SizedBox(
                        height: 5,
                      ),
                      RichText(
                        textAlign: TextAlign.center,
                        text: TextSpan(
                            text: "Didn't receive the code? ",
                            style:
                                TextStyle(color: Colors.black54, fontSize: 15),
                            children: [
                              TextSpan(
                                  text: " RESEND",
                                  // recognizer: onTapRecognizer,
                                  style: TextStyle(
                                      color: colorStyles["primary"],
                                      fontWeight: FontWeight.bold,
                                      fontSize: 16))
                            ]),
                      ),
                      SizedBox(
                        height: 7,
                      ),
                      Container(
                        margin: const EdgeInsets.symmetric(
                            vertical: 16.0, horizontal: 30),
                        child: ButtonTheme(
                          height: 50,
                          child: FlatButton(
                            onPressed: () async 
                              _onAlertrunnigbusiness(context);
                            ,
                            child: Center(
                                child: Text(
                              "VERIFY".toUpperCase(),
                              style: TextStyle(
                                  color: Colors.white,
                                  fontSize: 18,
                                  fontWeight: FontWeight.bold),
                            )),
                          ),
                        ),
                        decoration: BoxDecoration(
                          color: colorStyles["primary"],
                          borderRadius: BorderRadius.circular(5),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
          actions: <Widget>[
            FlatButton(
              child: Text('Close'),
              onPressed: () 
                Navigator.of(context).pop();
              ,
            ),
          ],
        );
      ,
    );
  

  _onAlertrunnigbusiness(context) 
    return showDialog<void>(
      context: context,
      barrierDismissible: false, // user must tap button!
      builder: (BuildContext context) 
        return AlertDialog(
          title: Text('Are you running Business?'),
          content: Container(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                SizedBox(
                  height: 10,
                ),
                Text(
                    "TUDO.App aims at Businesses bridging gaps between Business Service Providers and Consumers collaborate on unique technology platform. If you own a business, we strongly recommend, provide your business information to grow your customer base and expand your business services. Any questions? Call us @1-800-888-TUDO"),
                SizedBox(
                  height: 10,
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    FlatButton.icon(
                      icon: Icon(FontAwesomeIcons.arrowCircleRight),
                      label: Text('No'),
                      color: colorStyles["primary"],
                      textColor: Colors.white,
                      padding:
                          EdgeInsets.symmetric(vertical: 10, horizontal: 15),
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(7),
                      ),
                      onPressed: () 
                        NavigationHelper.navigatetoMainscreen(context);
                      ,
                    ),
                    SizedBox(height: 10),
                    FlatButton.icon(
                      icon: Icon(FontAwesomeIcons.arrowCircleRight),
                      label: Text('Yes'),
                      color: colorStyles["primary"],
                      textColor: Colors.white,
                      padding:
                          EdgeInsets.symmetric(vertical: 10, horizontal: 15),
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(7),
                      ),
                      onPressed: () 
                        NavigationHelper.navigatetoBspsignupcreen(context);
                      ,
                    ),
                  ],
                )
              ],
            ),
          ),
          actions: <Widget>[
            FlatButton(
              child: Text('Close'),
              onPressed: () 
                Navigator.of(context).pop();
              ,
            ),
          ],
        );
      ,
    );
  

  Widget _buildClearButton() 
    return new FlatButton(
      onPressed: () 
        _email.clear();
        _fn.clear();
        _ln.clear();
        _pho.clear();
        _add.clear();
        _pass.clear();
        _isError = false;

        setState(() 
          _isError = false;
        );
      ,
      child: Text(
        AppConstantsValue.appConst['signup']['clear']['translation'],
        style: TextStyle(letterSpacing: 1.5),
      ),
    );
  


  Widget _buildSignupButton(SignupBloc signupBloc, BuildContext context) 
    return GestureDetector(
      child: RoundrectButton.buildRoundedRectButton(
          AppConstantsValue.appConst['signup']['signup']['translation'],
          signUpGradients,
          false),
      onTap: () 
        //  _submit();
        final FormState form = formKey.currentState;
        form.save();
        if (form.validate() && person.termsAndCondition) 
          setState(() 
            _isLoading = true;
          );
          Map<String, dynamic> signupdata = 
            'email': person.email,
            'country': person.country,
            'countyCode': person.countryCode,
            'phoneNumber': person.phoneNumber,
            'firstName': person.firstname,
            'lastName': person.lastname,
            'password': person.password,
          ;
          _isError
              ? new Container(child: Text("Error"))
              : widget._signupBloc.dispatch(
                  SignupButtonClickedEvent(signupdata: signupdata),
                );
         else 
          print("Toast is printed");
          Errortoast().showColoredToast();
          setState(() 
            _isLoading = false;
            _validate = true;
          );
        
      ,
    );
  

  Widget _buildBackButton(BuildContext context) 
    return FlatButton(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Icon(Icons.arrow_back_ios),
          Text(AppConstantsValue.appConst['signup']['backtologin']
              ['translation'])
        ],
      ),
      onPressed: () => Navigator.pop(context, false),
    );
  

  Widget _buildLoader() 
    return Loader(
      color: colorStyles["primary"],
    );
  

  Widget content(signupBloc, context, List<Country> countries) 
    return SafeArea(
      top: false,
      bottom: false,
      child: Form(
        key: formKey,
        autovalidate: _validate,
        child: Scrollbar(
          child: SingleChildScrollView(
            dragStartBehavior: DragStartBehavior.down,
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            child: new Container(
              margin: EdgeInsets.fromLTRB(30, 0, 30, 0),
              child: new Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  _buildLogo(),
                  _buildEmailField(),
                  _buildCountry(countries),
                  _buildPhonefiled(),
                  _buildFnamefiled(),
                  _buildLnamefiled(),
                  _buildPasswordfiled(),
                  _buildTermsAndContionsCheck(),
                  SizedBox(),
                  _isError ? new Text('Fail to signup') : SizedBox(),
                  _isLoading ? _buildLoader() : SizedBox(),
                  _buildClearButton(),
                  _buildSignupButton(signupBloc, context),
                  _buildBackButton(context),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  

  @override
  Widget build(BuildContext context) 
    return BlocBuilder<SignupBloc, SignupState>(
      bloc: widget._signupBloc,
      builder: (
        BuildContext context,
        SignupState currentState,
      ) 
        if (currentState is UnSignupState) 
          return Container(
            height: MediaQuery.of(context).size.height / 1.10,
            width: MediaQuery.of(context).size.width / 1.10,
            child: Center(
              child: _buildLoader(),
            ),
          );
        

        if (currentState is ErrorSignupState) 
          _isLoading = false;
          _isError = true;
          showAlertBox = false;
          return Container(
            child: content(_signupBloc, context, _countries),
          );
        
        if (currentState is InSignupState) 
          _countries = currentState.countries.countries;
          return Container(child: content(_signupBloc, context, _countries));
        

        if (currentState is SignupButtonClickedEvent) 
          print('SignupButtonClickedEvent clicked');
          return Container();
        

        if (currentState is SignupSuccessState) 
          print(
              ' You are awesome. you have successfully registered without confirmation');
          print(currentState.signupUser.toJson());
          print("Hey Otp Is opned");
          WidgetsBinding.instance.addPostFrameCallback((_) 
            // _never();

            _onAlertotp();
          );
          _isLoading = false;
          showAlertBox = true;
          return Container(
            child: content(_signupBloc, context, _countries),
          );
        

        if (currentState is SignupVerficationOtp) 
          print('signup verficitaion otp button clicked');
          return Container();
        
        return Container(child: content(_signupBloc, context, _countries));
      ,
    );
  


class Person 
  String email = '';
  String country = '';
  String countryCode = '';
  String phoneNumber = '';
  String firstname = '';
  String lastname = '';
  String password = '';
  bool termsAndCondition = false;


class CountryModel 
  String country = '';
  String countryCode = '';

  CountryModel(
    this.country,
    this.countryCode,
  );

【问题讨论】:

【参考方案1】:

首先,如果您使用 Bloc 模式,则不能使用 setState。另一件事是你应该为一个集团创建一个新课程。在您的情况下,RegistrationBloc。然后导入 RxDart。

class SignupBloc

String _nameValue;

BehaviourSubject<String> _nameSubject = BehaviourSubject<String>();
Stream<String> get nameStream => _nameSubject.stream;
StreamController<String> _name = StreamController<String>();
Sink<String> get nameSink => _name.sink;

SignupBloc()

   //Inside the StreamController you can do anything to your value
   _name.stream.listen((name)
      if(name == "" || name == null)
         _nameSubject.addError("Please Enter Your Name");
      else
         _nameValue = name;
      
   

确保您首先创建并实例化您的集团

SignUpBloc _signUpBloc = SignInBloc();

您的文本字段应该是这样的

StreamBuilder<String>(
  stream: _signUpBloc.nameStream,
  builder: (context, snapshot) 
    return TextField(
      onChanged: (value) => _signUpBloc.nameSink.add(value),
      decoration: InputDecoration(
          labelText: "label", errorText: snapshot.error),
    );
  
);

【讨论】:

以上是关于如何在颤动中使用 Bloc 模式处理用户注册错误的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 BLoC 在颤动的屏幕之间传递数据?

应该将 Bloc 模式用于颤动文本字段,如果是的话如何?

为啥 AnimatedList 在颤动中不使用 bloc 状态的列表构建?

如何在颤动中使用带有 Bloc 的冷冻包?

使用 get_it 时颤动 bloc 处理

如何在颤动中使用 hydated_bloc 保持状态?