Collectives™ on Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most.
Learn more about Collectives
Teams
Q&A for work
Connect and share knowledge within a single location that is structured and easy to search.
Learn more about Teams
Ask Question
I am using stepper widget, and using a Form widget in each step of stepper with different form keys in order to validate each step ... the problem is that
this._formKey.currentState.validate()
always returns false even if the validation criteria fulfills for the current step ... following is the code that i use to build individual steps in stepper:
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;
Complete code for reference:
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;
–
–
–
focusNode: _focusNodeID,
hintText: Translations.of(context).cnic,
labelText: Translations.of(context).cnic,
controller: _controllerIdentifier,
keyboardType: TextInputType.number,
hasError: _isIdentifierRequired,
validator: _validateIdentifier,
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,
Now whenever you want to validate the fields call this function.
setState(() {
_autoValidate = true;
This will automatically verify all the fields in the form which have a validator function.
And your validator functions goes like this.
String _validateIdentifier(String value) {
if (value.isEmpty || value.length < 11 || value.length > 13) {
setState(() => _isIdentifierRequired = true);
return Translations.of(context).invalidInput;
return null;
–
–
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.