我正在使用步进器小部件,并在步进器的每个步骤中使用带有不同表单键的表单小部件来验证每个步骤......问题是this._formKey.currentState.validate()
即使验证标准满足当前步骤,它也总是返回 false......以下是我用来在步进器中构建各个步骤的代码:
Widget _buildCustomerInfoWidget() {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
CustomTextField(
focusNode: _focusNodeID,
hintText: Translations.of(context).cnic,
labelText: Translations.of(context).cnic,
controller: _controllerIdentifier,
keyboardType: TextInputType.number,
hasError: _isIdentifierRequired,
validator: (String t) => _validateIdentifier(t),
maxLength: 13,
),
CustomTextField(
focusNode: _focusNodeAmount,
hintText: Translations.of(context).amount,
labelText: Translations.of(context).amount,
controller: _controllerAmount,
keyboardType: TextInputType.number,
hasError: _isAmountRequired,
validator: (String t) => _validateAmount(t),
maxLength: 6,
),
],
));
}
Widget _buildCustomerVerificationWidget() {
return Form(
key: _formKeyOTP,
child: Column(
children: <Widget>[
Center(
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 16.0, vertical: 5.0),
child: Text(
Translations.of(context).helpLabelOTP,
style: new TextStyle(
color: Theme.of(context).primaryColor,
fontStyle: FontStyle.italic),
))),
CustomTextField(
focusNode: _focusNodeOTP,
hintText: Translations.of(context).otp,
labelText: Translations.of(context).otp,
controller: _controllerOTP,
keyboardType: TextInputType.number,
hasError: _isAmountRequired,
validator: (String t) => _validateOTP(t),
maxLength: 4,
obscureText: true,
),
],
));
}
List<Step> _buildStepperSteps(BuildContext context) {
List<Step> paymentSteps = [
Step(
title: Text(
Translations.of(context).infoStepLabel,
),
content: _buildCustomerInfoWidget(),
state: StepState.indexed,
isActive: _isStepActive),
Step(
title: Text(Translations.of(context).submitStepLabel),
state: StepState.indexed,
content: _buildCustomerVerificationWidget(),
isActive: !_isStepActive),
];
return paymentSteps;
}
完整代码供参考:
class PullPaymentPage extends StatefulWidget {
PullPaymentPage({Key key, this.title}) : super(key: key);
final String title;
@override
State<StatefulWidget> createState() {
return PullPaymentState();
}
}
class PullPaymentState extends State<PullPaymentPage> {
final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
final GlobalKey<FormState> _formKeyOTP = new GlobalKey<FormState>();
final TextEditingController _controllerIdentifier =
new TextEditingController();
final FocusNode _focusNodeOTP = new FocusNode();
final FocusNode _focusNodeID = new FocusNode();
final FocusNode _focusNodeAmount = new FocusNode();
final TextEditingController _controllerAmount = new TextEditingController();
final TextEditingController _controllerOTP = new TextEditingController();
bool _isIdentifierRequired = false;
bool _isAmountRequired = false;
bool _isOTPRequired = false;
bool _isStepActive = true;
int _currentStep = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(context),
body: new Builder(builder: (BuildContext context) {
return Container(child: _buildStepper(context));
}));
}
Stepper _buildStepper(BuildContext context) {
final Map functionMap = {0: _sendOTP, 1: _pullPayment};
List<Step> paymentSteps = _buildStepperSteps(context);
return Stepper(
steps: paymentSteps,
type: StepperType.vertical,
currentStep: this._currentStep,
onStepTapped: (step) {
setState(() {
if (step != 1) {
_currentStep = step;
}
(_currentStep == 0) ? _isStepActive = true : _isStepActive = false;
});
},
onStepContinue: () {
functionMap[_currentStep]().then((value) {
if (value == true) {
setState(() {
_currentStep++;
});
if (_currentStep == 1) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(Translations.of(context).otpSuccess),
duration: Duration(seconds: 4),
));
}
}
});
},
onStepCancel: () {
setState(() {
_currentStep == 0 ? Navigator.of(context).pop() : stepBack();
});
},
);
}
void stepBack() {
_isStepActive = true;
_currentStep--;
}
Map<int, Function> createFunctionMap() {
Map functionMap = {1: _sendOTP(), 2: _pullPayment()};
return functionMap;
}
AppBar _buildAppBar(BuildContext context) {
return AppBar(
title: Text(
Translations.of(context).pullPayment,
),
backgroundColor: Theme.of(context).primaryColorDark,
leading: new BackButton(),
);
}
Widget _buildCustomerInfoWidget() {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
CustomTextField(
focusNode: _focusNodeID,
hintText: Translations.of(context).cnic,
labelText: Translations.of(context).cnic,
controller: _controllerIdentifier,
keyboardType: TextInputType.number,
hasError: _isIdentifierRequired,
validator: (String t) => _validateIdentifier(t),
maxLength: 13,
),
CustomTextField(
focusNode: _focusNodeAmount,
hintText: Translations.of(context).amount,
labelText: Translations.of(context).amount,
controller: _controllerAmount,
keyboardType: TextInputType.number,
hasError: _isAmountRequired,
validator: (String t) => _validateAmount(t),
maxLength: 6,
),
],
));
}
Widget _buildCustomerVerificationWidget() {
return Form(
key: _formKeyOTP,
child: Column(
children: <Widget>[
Center(
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 16.0, vertical: 5.0),
child: Text(
Translations.of(context).helpLabelOTP,
style: new TextStyle(
color: Theme.of(context).primaryColor,
fontStyle: FontStyle.italic),
))),
CustomTextField(
focusNode: _focusNodeOTP,
hintText: Translations.of(context).otp,
labelText: Translations.of(context).otp,
controller: _controllerOTP,
keyboardType: TextInputType.number,
hasError: _isAmountRequired,
validator: (String t) => _validateOTP(t),
maxLength: 4,
obscureText: true,
),
],
));
}
List<Step> _buildStepperSteps(BuildContext context) {
List<Step> paymentSteps = [
Step(
title: Text(
Translations.of(context).infoStepLabel,
),
content: _buildCustomerInfoWidget(),
state: StepState.indexed,
isActive: _isStepActive),
Step(
title: Text(Translations.of(context).submitStepLabel),do
state: StepState.indexed,
content: _buildCustomerVerificationWidget(),
isActive: !_isStepActive),
];
return paymentSteps;
}
String _validateIdentifier(String value) {
if (value.isEmpty || value.length < 11 || value.length > 13) {
setState(() => _isIdentifierRequired = true);
return Translations.of(context).invalidInput;
}
return "";
}
String _validateAmount(String value) {
if (value.isEmpty) {
setState(() => _isAmountRequired = true);
return Translations.of(context).amountRequiredError;
} else if (!RegexHelpers.amountValidatorRegex.hasMatch(value)) {
return Translations.of(context).validAmountError;
}
return "";
}
String _validateOTP(String value) {
if (value.isEmpty || value.length < 4) {
setState(() => _isOTPRequired = true);
return Translations.of(context).invalidOtp;
}
return "";
}
bool _validateInfoForm() {
_isOTPRequired = false;
_isAmountRequired = false;
_isIdentifierRequired = false;
if (!this._formKey.currentState.validate()) {
return false;
}
_formKey.currentState.save();
return true;
}
bool _validateOtpForm() {
_isOTPRequired = false;
_isAmountRequired = false;
_isIdentifierRequired = false;
if (!this._formKeyOTP.currentState.validate()) {
return false;
}
_formKeyOTP.currentState.save();
return true;
}
Future<bool> _sendOTP() async {
bool sendOTP = false;
setState(() {
_isIdentifierRequired = false;
_isAmountRequired = false;
_isOTPRequired = false;
});
if (!_validateInfoForm()) {
setState(() {
_validateAmount(_controllerAmount.text);
_validateIdentifier(_controllerIdentifier.text);
});
if (_validateAmount(_controllerAmount.text).isNotEmpty ||
_validateIdentifier(_controllerIdentifier.text).isNotEmpty) {
return false;
}
}
try {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
content: ListTile(
leading: CircularProgressIndicator(),
title: Text(Translations.of(context).processingSendOtpDialog),
),
),
);
TransactionApi api =
new TransactionApi(httpDataSource, authenticator.sessionToken);
sendOTP = await api.sendOTP(_controllerIdentifier.text);
if (sendOTP) {
_isStepActive = false;
_controllerOTP.clear();
}
Navigator.of(context).pop();
} catch (exception) {
await showAlertDialog(context, Translations.of(context).pullPayment,
'${exception.message}');
Navigator.of(context).pop();
return false;
}
return sendOTP;
}
Future<bool> _pullPayment() async {
Result pullPaymentResponse = new Result();
setState(() {
_isIdentifierRequired = false;
_isAmountRequired = false;
_isOTPRequired = false;
});
if (!_validateOtpForm()) {
setState(() {
_validateOTP(_controllerAmount.text);
});
if (_validateAmount(_controllerAmount.text).isNotEmpty ||
_validateIdentifier(_controllerIdentifier.text).isNotEmpty ||
_validateOTP(_controllerOTP.text).isNotEmpty) {
return false;
}
}
try {
setState(() {
_isOTPRequired = false;
});
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
content: ListTile(
leading: CircularProgressIndicator(),
title: Text(Translations.of(context).processingPaymentDialog),
),
),
);
TransactionApi api =
new TransactionApi(httpDataSource, authenticator.sessionToken);
pullPaymentResponse = await api.pullPayment(_controllerIdentifier.text,
_controllerAmount.text, _controllerOTP.text);
Navigator.of(context).pop();
} catch (exception) {
await showAlertDialog(context, Translations.of(context).pullPayment,
'${exception.message}');
Navigator.of(context).pop();
return false;
}
if (successResponseCodes.contains(pullPaymentResponse.responseCode)) {
await showAlertDialog(context, Translations.of(context).pullPayment,
'${pullPaymentResponse.description}');
Navigator.pop(context);
return true;
}
return false;
}
}