Reputation: 2249
i am using stepper widget in order to collect info from user and validate it, i need to call an API at each step hence validate each field in a step at every continue button ... i am using form state and form widget but the issue is that it validates entire fields in all steps in stepper... how can i validate only individual step in a stepper? i went through the documentation in Stepper and State classes in stepper.dart but there is no supporting function there
following is the code
class SubmitPayment extends StatefulWidget {
SubmitPayment({Key key, this.identifier, this.amount, this.onResendPressed})
: super(key: key);
final String identifier;
final String amount;
final VoidCallback onResendPressed;
@override
State<StatefulWidget> createState() {
return _SubmitPaymentState();
}
}
class _SubmitPaymentState extends State<SubmitPayment> {
final GlobalKey<FormState> _formKeyOtp = GlobalKey<FormState>();
final FocusNode _otpFocusNode = FocusNode();
final TextEditingController _otpController = TextEditingController();
bool _isOTPRequired = false;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: 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: TextStyle(
color: Theme.of(context).primaryColor,
fontStyle: FontStyle.italic),
))),
CustomTextField(
icon: Icons.vpn_key,
focusNode: _otpFocusNode,
hintText: Translations.of(context).otp,
labelText: Translations.of(context).otp,
controller: _otpController,
keyboardType: TextInputType.number,
hasError: _isOTPRequired,
validator: (String t) => _validateOTP(t),
maxLength: AppConstants.otpLength,
obscureText: true,
),
Center(
child: ButtonBar(
mainAxisSize: MainAxisSize.max,
alignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text(Translations.of(context).resendOtpButton),
color: Colors.white,
textColor: Theme.of(context).primaryColor,
onPressed: widget.onResendPressed,
),
RaisedButton(
child: Text(
Translations.of(context).payButton,
),
onPressed: _doPullPayment,
),
],
)),
])),
);
}
String _validateOTP(String value) {
if (value.isEmpty || value.length < AppConstants.otpLength) {
setState(() => _isOTPRequired = true);
return Translations.of(context).invalidOtp;
}
return "";
}
bool _validateOtpForm() {
_formKeyOtp.currentState.save();
return this._formKeyOtp.currentState.validate();
}
Future<void> _doPullPayment() async {
setState(() {
_isOTPRequired = false;
});
if (!_validateOtpForm()) 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 =
TransactionApi(httpDataSource, authenticator.sessionToken);
String responseMessage = await api.doPullPayment(
widget.identifier,
widget.amount,
_otpController.text,
TransactionConstants.transactionCurrency);
Navigator.of(context).pop();
await showAlertDialog(
context, Translations.of(context).pullPayment, '$responseMessage');
Navigator.pop(context);
} catch (exception) {
await showAlertDialog(context, Translations.of(context).pullPayment,
'${exception.message}');
Navigator.of(context).pop();
}
}
Upvotes: 5
Views: 10285
Reputation: 16319
One approach is to use a separate Form
for each step.
To handle that, use a list of GlobalKey<FormState>
which you can index based on _currentStep
, then call validate()
in onStepContinue
:
List<GlobalKey<FormState>> _formKeys = [GlobalKey<FormState>(), GlobalKey<FormState>(), …];
…
Stepper(
currentStep: _currentStep,
onStepContinue: () {
setState(() {
if (_formKeys[_currentStep].currentState?.validate()) {
_currentStep++;
}
});
},
steps:
Step(
child: Form(key: _formKeys[0], child: …),
This implies the following:
save
instead of just validating;Form
s to several widgets. If you do so, do not confuse the key
parameter that every Widget
has. Pass the formKey
as an unnamed parameter to avoid confusion.Upvotes: 14
Reputation: 41
It's been long since this question was asked. I hope my answer can help. To do this, I created a List<GlobalKey> then in the onContinue of the Stepper I did something as
final List<GlobalKey<FormState>> _formKeys = [
GlobalKey<FormState>(),
GlobalKey<FormState>(),
GlobalKey<FormState>(),
GlobalKey<FormState>()
]; continued() {
if(_formKeys[_currentStep].currentState!.validate()) {
switch(_currentStep){
case 0:
setSender();
break;
case 1:
setReceiver();
break;
}
}
}
Upvotes: 1
Reputation: 2249
So i solved this as follows:
The problem was that i was returning an *empty string ("") * if the my logic was valid, where as validate method of FormState expects each validator method, associated with TextFormField to return null if validation is passed.
i changed following
String _validateOTP(String value) {
if (value.isEmpty || value.length < AppConstants.otpLength) {
setState(() => _isOTPRequired = true);
return Translations.of(context).invalidOtp;
}
return "";
}
to
String _validateOTP(String value) {
if (value.isEmpty || value.length < AppConstants.otpLength) {
setState(() => _isOTPRequired = true);
return Translations.of(context).invalidOtp;
}
return null;
}
and it worked all fine then.
Refer to this link for details "If there is an error with the information the user has provided, the validator function must return a String containing an error message. If there are no errors, the function should not return anything."
Upvotes: 1