如何在颤动中使用 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 模式处理用户注册错误的主要内容,如果未能解决你的问题,请参考以下文章