Dart / flutter: DropdownButton causes exception when value is changed - dart

I have written a pretty extensive form using DropdownButton and TextField widgets. The concept is that I have a StatefulWidget, where the class of State<StatefulWidget> contains 2 methods that return the widget I want to build. This way I can easily access and use the entered data and pass it along a function to compose an e-mail out of them.
However, when I select an item from the options, the framework throws an exception during the rebuild. I put in some log functions, and it shows that the setState() method successfully saves the value to selectedValue variable.
Widget buildMultiChoiceInputRow(var label, List<String> values) {
final List<String> options = values.toList();
selection = options.first;
final dropDownMenuOptions = options.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList();
return new Column(
children: <Widget>[
new Row(
children: <Widget>[
new Expanded(
child: new Container(
padding:
const EdgeInsets.only(left: 5.0, top: 2.0, right: 5.0),
child: new Text(label, style: commonInfoCardInfoTextBlack16Bold)),
),
],
),
new Row(
children: <Widget>[
new Expanded(
child: new Container(
padding: const EdgeInsets.only(left: 5.0, right: 5.0),
child: new DropdownButton(
value: selectedValue,
items: dropDownMenuOptions,
onChanged: (selection) {
setState(() {
selectedValue = selection;
switch (label) {
case labelVirtualAdoption:
tempAdoptionType =
composeMultiChoiceAnswer(label, selection);
print(selection);
print(selectedValue);
break;
case labelAskedAboutSpecies:
tempAskedAboutSpecies =
composeMultiChoiceAnswer(label, selection);
break;
case labelHouseOrFlat:
tempHouseOrFlat =
composeMultiChoiceAnswer(label, selection);
break;
....
default:
break;
}
});
}),
),
)
],
),
new Divider(color: Colors.transparent)
],
);
}
Here is the exception:
I/flutter (20998): The following assertion was thrown building AdoptionInput(dirty, state: AdoptionInputState#3cc80):
I/flutter (20998): 'package:flutter/src/material/dropdown.dart': Failed assertion: line 481 pos 15: 'value == null ||
I/flutter (20998): items.where((DropdownMenuItem<T> item) => item.value == value).length == 1': is not true.
And here is the stack, showing that the exception is thrown during the rebuild:
I/flutter (20998): #2 new DropdownButton (package:flutter/src/material/dropdown.dart)
I/flutter (20998): #3 AdoptionInputState.buildMultiChoiceInputRow (package:osszefogasaszanhuzokert/adoptionPageUtilities.dart:443:28)
I/flutter (20998): #4 AdoptionInputState.build (package:osszefogasaszanhuzokert/adoptionPageUtilities.dart:639:11)
I/flutter (20998): #5 StatefulElement.build (package:flutter/src/widgets/framework.dart:3730:27)
I/flutter (20998): #6 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3642:15)
I/flutter (20998): #7 Element.rebuild (package:flutter/src/widgets/framework.dart:3495:5)
I/flutter (20998): #8 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2242:33)
The problem seems really similar to a former bug in flutter, but if I try to initialize the selection and selectedValue in initState(), the same exception will be thrown right as the form is built for the first time.
What am I missing here?

Your "value" for DropdownButton should be set to 'null' or or be one from the values list.
DropdownButton(
value: null,
isDense: true,
onChanged: (String newValue) {
// somehow set here selected 'value' above whith
// newValue
// via setState or reactive.
},
items: ['yellow', 'brown', 'silver'].map((String value) {
return DropdownMenuItem(
value: value,
child: Text(value),
);
}).toList(),
),
So for my example DropdownButton value should be set to null or be 'yellow' or 'brown' or 'silver'.

Extending above answer with the second case where I was stuck.
Your "value" for DropdownButton should be set to 'null' or be one from the values list.
Your 'values' should also be different in every item value.
for example: Avoid this
items.add(DropdownMenuItem(
value: 1.toString(),
child: Text(1.toString()),
));
items.add(DropdownMenuItem(
value: 1.toString(),
child: Text(1.toString()),
));
Avoid repeating the values.

A bit off time, but, the code worked when I passed null as the "value" but I was having this problem when I placed a value that was included on the "Items". The problem was that the "Items" had a duplicate value, so it seems that you should provide all different items in the list passed to the "Items".

here is the right way:
import 'package:flutter/material.dart';
class RegisterFragments extends StatefulWidget {
RegisterFragments({Key key, this.step}) : super(key: key);
final int step;
_RegisterFragmentsState createState() => _RegisterFragmentsState();
}
class _RegisterFragmentsState extends State<RegisterFragments> {
Map<String, bool> values = {"abc": false, "def": true, "ghi": false};
List<String> _do = ['One', 'Two', 'Free', 'Four'];
String _dropdownValue = 'One';
#override
Widget build(BuildContext context) {
switch (widget.step) {
case 0:
return buildDo();
break;
case 1:
return Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: values.length,
itemBuilder: (BuildContext context, int index) {
switch (widget.step) {
case 0:
return buildDo();
break;
case 1:
return buildService(context, index);
break;
default:
return Container();
break;
}
},
),
);
break;
default:
return Container();
break;
}
}
Widget buildService(BuildContext context, int index) {
String _key = values.keys.elementAt(index);
return Container(
child: Card(
child: CheckboxListTile(
title: Text(_key),
onChanged: (bool value) {
setState(() {
values[_key] = value;
});
},
value: values[_key],
),
),
);
}
Widget buildDo() {
return DropdownButton<String>(
isExpanded: true,
hint: Text("Service"),
items: _do.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String newValue) {
setState(() {
this._dropdownValue = newValue;
});
},
value: _dropdownValue,
);
}
}
list and dorpdown value
List<String> _do = ['One', 'Two', 'Free', 'Four'];
String _dropdownValue = 'One';
dropdown items
tems: _do.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>( **
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String newValue) {
setState(() {
this._dropdownValue = newValue;
});
},
value: _dropdownValue,

Use var to declare variable instead of String. Now you don't need to set the default value to null.
var dropdownvalue;
DropdownButton<String>(
value: dropdownvalue,
icon: Icon(Icons.keyboard_arrow_down),
iconSize: 28,
elevation: 20,
onChanged: (String newval){
setState((){
dropdownvalue = newval;
});
},
items: <String>["Registration","Verification", "ArenaRun"]
.map<DropdownMenuItem<String>>((String value){
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),

I was doing a value.toString() and the null was being converted to "null"!

Related

Unable to identify null value in flutter ios app

I'm trying to debug an ios app that I wrote with flutter. The android version works fine, but the ios version seems to constantly spit up when getting to a specific screen by pushing a button. The error is:
Another exception was thrown: FormatException: Invalid number (at character 1)
My research shows there is a null value somewhere on the page, but I can't see it, and I can't figure out how to debug inside the ios simulator from Android Studio. Can anyone suggest?
Code for the page I am going to is below:
//1) fO2(default to 4.76)
//2) Bar vs PSI?? Probably in V2
//3) Owner Name
import 'package:ccrchecklist/main.dart';
import 'package:flutter/material.dart';
import './myUtils.dart';
class settingsPage extends StatefulWidget {
#override
_settingsPage createState() => _settingsPage();
}
class _settingsPage extends State{
final _name = TextEditingController(text: myUtils.getSharedPref('name'));
int _radioBarPSIValue = int.parse(myUtils.getSharedPref('bar'));
int _radioDecimalValue = int.parse(myUtils.getSharedPref('decimal'));
final _formKey = GlobalKey<FormState>();
bool _validator1 = false;
bool _validator2 = false;
bool _validator3 = false;
String name = '';
void _handleBarPSIRadioValueChange(int value) {
setState(() {
_radioBarPSIValue = value;
print("Setting Bar PSI");
switch (_radioBarPSIValue) {
case 0:
myUtils.setSharedPref('bar', '0'); //Set to Bar
break;
case 1:
myUtils.setSharedPref('bar', '1'); //Set to PSI
break;
}
});
}
void _handleDecimalRadioValueChange(int value) {
setState(() {
_radioDecimalValue = value;
print("Setting Bar PSI");
switch (_radioDecimalValue) {
case 0:
myUtils.setSharedPref('decimal', '0'); //Set to Bar
break;
case 1:
myUtils.setSharedPref('decimal', '1'); //Set to PSI
break;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView(
padding: EdgeInsets.symmetric(horizontal: 24.0),
children: <Widget>[
SizedBox(height: 120.0),
//Name
Form(
key: _formKey,
child: Column(
children: <Widget>[
//Name field
new TextFormField(
keyboardType: TextInputType.text,
controller: _name,
autovalidate: _validator1,
decoration: const InputDecoration(
icon: Icon(Icons.person),
hintText: 'Your Name',
labelText: 'Name',
),
onChanged: (String value) {
name = _name.text;
setState(() {
_validator1 = true;
});
},
validator: (String value) {
if(value.isEmpty){
return "Field is required";
}
else {
return null;
}
},
),
//Bar or PSI
Row(
children: <Widget>[
Radio(
value: 0,
groupValue: _radioBarPSIValue,
onChanged: _handleBarPSIRadioValueChange,
),
Text(
'Bar',
style: new TextStyle(fontSize: 16.0),
),
Radio(
value: 1,
groupValue: _radioBarPSIValue,
onChanged: _handleBarPSIRadioValueChange,
),
Text(
'PSI',
style: new TextStyle(fontSize: 16.0),
),
]
),
ButtonBar(
children: <Widget>[
//Clear Button
FlatButton(
child: Text('Cancel'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),);
},
),
RaisedButton(
child: Text('SAVE'),
onPressed: () {
print(name);
_handleBarPSIRadioValueChange(_radioBarPSIValue);
if(name != '')
myUtils.setSharedPref('name', name);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),);
}
),
]
),
],
),
),])));
}
}```

DropdownButton is not changing the value on onChanged

The DropdownButton is not changing the value of dropdown after another selection is made from the dropdown. Below is my code.
Flexible(
child: Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
child: DropdownButton(
hint: Text('Select'),
items: list_dropdown,
onChanged: (val) {
setState(() {
wd = val;
});
},
value: wd,
)),
)
In the initState I'm setting the value variable
#override
void initState() {
// TODO: implement initState
super.initState();
wd = 0;
}
while I create the variale in the global scope
int wd;
Where am I going wrong?
Why did you create a variable in the global scope? If you want to mutate your variable called wd with setState(), you have to put it in your state class.
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
// Here wd in my state class
int wd = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Cat Attack"),
),
body: Center(
child: DropdownButton(
value: wd,
onChanged: (val) {
setState(() {
wd = val;
});
},
items: [
DropdownMenuItem(
child: Text('1'),
value: 1,
),
DropdownMenuItem(
child: Text('2'),
value: 2,
),
],
),
));
}
}
You can override the value of wd in initState(), once you've defined in state.
class _AppState extends State<App> {
int wd;
#override
void initState() {
super.initState();
wd = 2;
}
Keep in mind that, your "value" for DropdownButton should be set to 'null' or be one from the values list. So if you set it to 5, rather then 1, 2 or null, which are the values in my DropdownMenuItem's, you will get this error:
I/flutter (15227): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (15227): The following assertion was thrown building App(dirty, state: _AppState#30354):
I/flutter (15227): 'package:flutter/src/material/dropdown.dart': Failed assertion: line 560 pos 15: 'items == null ||
I/flutter (15227): items.isEmpty || value == null || items.where((DropdownMenuItem item) => item.value ==
I/flutter (15227): value).length == 1': is not true.

Flutter how to update text on dropdownbutton when using Sqflite to populate the list

I have no problem populating the list from Sqflite database on DropdownButton. My only problem is updating the text once it's selected. It kept showing 'Airport' and I'm still learning to work with Object instead of String. I just couldn't figure that out.
Here's the code:
String selectedAirport;
AirportModel _currentAirport;
...
children: <Widget>[
FutureBuilder<List<AirportModel>>(
future: db.getAllAirports(),
builder: (BuildContext context, AsyncSnapshot<List<AirportModel>> snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return DropdownButton<AirportModel>(
items: snapshot.data
.map((airportItem) =>
DropdownMenuItem<AirportModel>(
value: airportItem,
child: Text(airportItem.airportName),
))
.toList(),
onChanged: (AirportModel value) {
setState(() {
_currentAirport = value;
selectedAirport = _currentAirport.airportName;
});
},
hint: Text("Airport"),
);
}),
DropdownButton has a property value. use it like value=_currentAirport
return DropdownButton<AirportModel>(
value:_currentAirport,
items: snapshot.data
.map((airportItem) =>
DropdownMenuItem<AirportModel>(
value: airportItem,
child: Text(airportItem.airportName),
))
.toList(),
onChanged: (AirportModel value) {
setState(() {
_currentAirport = value;
selectedAirport = _currentAirport.airportName;
});
},
hint: Text("Airport"),
);
Maybe items didn't reach yet or empty when value is set to DropdownButton. is _currentAirport initialized to some other value already?
Can you try like this? Also check if the items list are empty
items: snapshot.data == null ? null : _currentAirport
You can declare a Future and init in initState and in FutureBuilder use this future.
AirportModel _currentAirport;;
Future _future;
#override
void initState() {
_future = db.getAllAirports();
super.initState();
}
body: FutureBuilder<List<AirportModel>>(
future: _future,
You can use stream builder. Please check the example below.
class DropDownMenu extends StatefulWidget {
#override
_DropDownMenuState createState() => _DropDownMenuState();
}
class _DropDownMenuState extends State<DropDownMenu> {
var _currentSelectedValue;
final _dbHelper = DatabaseHelper.instance;
LoginPageManager _loginPageManager = new LoginPageManager();
final ValueNotifier<List<DropdownMenuItem<String>>> _dropDownMenuItems =
ValueNotifier<List<DropdownMenuItem<String>>>([]);
#override
void initState() {
_updateList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
width: 300,
height: 50,
margin: const EdgeInsets.only(top: 00.0),
child: ValueListenableBuilder(
builder: (BuildContext context, List<DropdownMenuItem<String>> list,
Widget child) {
return Container(
child: DropdownButton<String>(
hint: Text("Please Select a Server"),
value: _currentSelectedValue,
onChanged: (value) {
setState(() {
_currentSelectedValue = value;
});
},
items: list),
);
},
valueListenable: _dropDownMenuItems,
),
);
}
_updateList() async {
print("Update server has been called");
_dropDownMenuItems.value.clear();
List<Map<String, dynamic>> x = await _dbHelper.queryAllRows();
_dropDownMenuItems.value.add(_getAddServerButton());
x.forEach((element) {
_dropDownMenuItems.value.add(_getDropDownWidget(element));
});
}
DropdownMenuItem<String> _getDropDownWidget(Map<String, dynamic> map) {
int id = map['yxz'];
String text =
map['xyz'];
String value = map['zyx'];
return DropdownMenuItem<String>(
value: value,
child: Container(
width: 270,
child: Row(
children: [_getText(text), _getRemoveButton(id), _getEditButton(id)],
),
));
}
}
To make sure api data is not null:
child: _identity1 != null
? DropdownButtonFormField<dynamic>(
validator: (value) => value == null ? 'field required' : null

Drop down button in flutter not switching values to the selected value

I've recently started programming using dart and flutter and everything has been going smoothly for my app, although recently i wanted to add drop down menu to provide the user with multiple options to pick from. everything worked as planned however when i pick a value from the list it doesn't change the value in the box, it goes back to the hint or an empty box. any help would be appreciated!
here is my code for the dropdownbutton:
Widget buildDropdownButton() {
String newValue;
return new Padding(
padding: const EdgeInsets.all(24.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new ListTile(
title: const Text('Frosting'),
trailing: new DropdownButton<String>(
hint: Text('Choose'),
onChanged: (String changedValue) {
newValue=changedValue;
setState(() {
newValue;
print(newValue);
});
},
value: newValue,
items: <String>['None', 'Chocolate', 'Vanilla', 'ButterCream']
.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList()),
),
],
),
);
}
The error is because you are declaring a method variable newValue you must declare that variable as global inside your StatefulWidget.
String newValue;
Widget buildDropdownButton() {
return new Padding(
padding: const EdgeInsets.all(24.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new ListTile(
title: const Text('Frosting'),
trailing: new DropdownButton<String>(
hint: Text('Choose'),
onChanged: (String changedValue) {
newValue=changedValue;
setState(() {
newValue;
print(newValue);
});
},
value: newValue,
items: <String>['None', 'Chocolate', 'Vanilla', 'ButterCream']
.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList()),
),
],
),
);
}
Faced same issue and none of the answers worked. Then, I found the solution in one of my old projects.
I was using it in a AlertDialog here.
So, Change DropdownButton to DropdownButtonFormField
and add onSaved exactly as onChanged:
onSaved: (value) {
setState(() {
_selectedValue = value;
});
}
That's it. It will start working.
I had this problem although I was already using the solution above.
for anyone who has this problem and the above solution does not work, try separating FutureBuilder from the dropdown. this is how your final code should look like:
class TheFuture extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: myFuture(),
builder: (ctx, snp) {
if (!snp.hasData) return LoadingLine();
return TheBuilder(snp.data);
},
);
}
}
class TheBuilder extends StatefulWidget {
const TheBuilder(this.mp);
final Map<String, dynamic> mp;
#override
_MessageUSScreenFilterBodyState createState() =>
_MessageUSScreenFilterBodyState();
}
class _MessageUSScreenFilterBodyState extends State<MessageUSScreenFilterBody> {
int _selectedId;
#override
Widget build(BuildContext context) {
return DropdownButton<int>(
selectedItemBuilder: (context) =>
widget.mp['myData'].map((e) => Text(e.type)).toList(),
items: widget.mp['myData']
.map(
(e) => DropdownMenuItem(
child: Text(e.type),
value: e.id,
),
)
.toList(),
value: _selectedId,
onChanged: (int _id) {
setState(() {
_selectedId = _id;
});
},
);
}
}
wrap dropdown button with StatefulBuilder and initialise newValue outside build method.
StatefulBuilder(
builder: (context, setState) => AlertDialog(
title: Text("Change Status"),
content: Container(
padding: EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1),
borderRadius: BorderRadius.circular(5)),
child: DropdownButtonHideUnderline(
child: DropdownButton(
hint: Text('Choose'),
onChanged: (String changedValue) {
setState(() {
newValue = changedValue;
print(newValue);
});
},
value: newValue,
items: <String>[
'None',
'Chocolate',
'Vanilla',
'ButterCream'
].map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList()),
),
),
),
));

flutter stepper widget - validating fields in individual steps

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();
}
}
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:
Since you're calling an API at the end, you need to check if you're validating the last step, and save instead of just validating;
You probably want to factor our the Forms 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.
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."
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;
}
}
}

Resources