I am trying to pass data from one screen to another, but I keep getting a null exception. Whenever I fill in the form on the first screen and proceed to next screen, I get a `
NoSuchMethodError: The getter 'storeNumber' was called on null
`
My variables class is ==> This entity class has variables that I populate using a form in the following class:
class StoreData {
String _storeNumber;
String _repName;
String _repCell;
DateTime _transactionDate = new DateTime.now();
StoreData(
this._storeNumber, this._repName, this._repCell, this._transactionDate);
String get storeNumber => _storeNumber;
set storeNumber(String value) {
_storeNumber = value;
}
String get repName => _repName;
DateTime get transactionDate => _transactionDate;
set transactionDate(DateTime value) {
_transactionDate = value;
}
String get repCell => _repCell;
set repCell(String value) {
_repCell = value;
}
set repName(String value) {
_repName = value;
}
}
The main class (in this case this is the first screen that sends data to second screen) includes the following code:
This class has a form that takes in 3 inputs and send them to second screen.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'FeedBack.dart';
import 'StoreData.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstScreen(),
));
}
//get our entity class
StoreData storeDate;
// get variables from entity class
String storeNumber = storeDate.storeNumber;
String repName = storeDate.repName;
String repCell = storeDate.repCell;
DateTime transactionDate = storeDate.transactionDate;
class FirstScreen extends StatefulWidget {
#override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
GlobalKey<FormState> _key = GlobalKey();
bool _validate = false;
_sendData() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FeedBack(
storeData: new StoreData(
storeNumber, repName, repCell, transactionDate))),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text('Test App'),
),
body: new SingleChildScrollView(
child: new Container(
margin: new EdgeInsets.all(15.0),
child: new Form(
key: _key,
autovalidate: _validate,
child: formUI(),
),
),
),
),
);
}
Widget formUI() {
return new Column(
children: <Widget>[
new TextFormField(
decoration: new InputDecoration(hintText: 'Store Number'),
keyboardType: TextInputType.number,
validator: validateRepCell,
onSaved: (String val) {
storeNumber = val;
}),
new TextFormField(
decoration: new InputDecoration(hintText: 'Rep Full Name'),
validator: validateRepName,
onSaved: (String val) {
repName = val;
}),
new TextFormField(
decoration: new InputDecoration(hintText: 'Rep Phone Number'),
keyboardType: TextInputType.number,
validator: validateRepCell,
onSaved: (String val) {
repCell = val;
}),
new SizedBox(height: 15.0),
new RaisedButton(
onPressed: _sendData,
child: new Text('Proceed'),
)
],
);
}
// Validate Fields
String validateRepCell(String value) {
// String patttern = r'(^[a-zA-Z ]*$)';
RegExp regExp = new RegExp(r'^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$');
if (value.length == 0) {
return "Store Number is Required";
} else if (!regExp.hasMatch(value)) {
return "Store Number must be only have numbers";
}
return null;
}
String validateRepName(String value) {
String patttern = r'(^[a-zA-Z ]*$)';
RegExp regExp = new RegExp(patttern);
if (value.length == 0) {
return "Rep Name is Required";
} else if (!regExp.hasMatch(value)) {
return "Name must be a-z and A-Z";
}
return null;
}
}
The second screen's code is here:
class FeedBack extends StatelessWidget {
final StoreData storeData;
FeedBack({Key key, #required this.storeData}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FeedBack Screen"),
),
body: new Container(
child: new Column(
children: <Widget>[
new RaisedButton(
onPressed: _sendToDatabase,
child: new Text('Press Me'),
),
new Text("${storeData.storeNumber}"),
],
),
),
);
}
_sendToDatabase() {
Firestore.instance.runTransaction((Transaction transaction) async {
CollectionReference reference = Firestore.instance.collection('Stores');
await reference.add({"test": "test", "testII": "test"});
});
}
}
I have been trying to solve this problem for a week now, but given my new experience with Dart and Flutter framework, it has been tough !
Any help would be appreciated,
You can use the following approach.
Remove the following lines from your code:
//get our entity class
StoreData storeDate;
As initially there will be no instance of StoreData available right now.
Now, declare new variables like the following:
String storeNumber;
String repName;
String repCell;
DateTime transactionDate;
And then assign the form values to them in onSaved method.
So when your form will be submitted, these values will be used for creating new StoreData and it will be passed to the Second page.
Here is the code for your main.dart file:
import 'package:flutter/material.dart';
import 'FeedBack.dart';
import 'StoreData.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstScreen(),
));
}
// get variables from entity class
String storeNumber;
String repName;
String repCell;
DateTime transactionDate = DateTime.now();
class FirstScreen extends StatefulWidget {
#override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
GlobalKey<FormState> _key = GlobalKey();
bool _validate = false;
_sendData() {
_key.currentState.save();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FeedBack(
storeData: StoreData(
storeNumber, repName, repCell, transactionDate))),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text('Test App'),
),
body: new SingleChildScrollView(
child: new Container(
margin: new EdgeInsets.all(15.0),
child: new Form(
key: _key,
autovalidate: _validate,
child: formUI(),
),
),
),
),
);
}
Widget formUI() {
return new Column(
children: <Widget>[
new TextFormField(
decoration: new InputDecoration(hintText: 'Store Number'),
keyboardType: TextInputType.number,
validator: validateRepCell,
onSaved: (String val) {
storeNumber = val;
}),
new TextFormField(
decoration: new InputDecoration(hintText: 'Rep Full Name'),
validator: validateRepName,
onSaved: (String val) {
repName = val;
}),
new TextFormField(
decoration: new InputDecoration(hintText: 'Rep Phone Number'),
keyboardType: TextInputType.number,
validator: validateRepCell,
onSaved: (String val) {
repCell = val;
}),
new SizedBox(height: 15.0),
new RaisedButton(
onPressed: _sendData,
child: new Text('Proceed'),
)
],
);
}
// Validate Fields
String validateRepCell(String value) {
// String patttern = r'(^[a-zA-Z ]*$)';
RegExp regExp = new RegExp(r'^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$');
if (value.length == 0) {
return "Store Number is Required";
} else if (!regExp.hasMatch(value)) {
return "Store Number must be only have numbers";
}
return null;
}
String validateRepName(String value) {
String patttern = r'(^[a-zA-Z ]*$)';
RegExp regExp = new RegExp(patttern);
if (value.length == 0) {
return "Rep Name is Required";
} else if (!regExp.hasMatch(value)) {
return "Name must be a-z and A-Z";
}
return null;
}
}
Related
I have a form with some inputs. I am using a GlobalKey<FormState> to handle submissions and validation and so on.
One of the fields is supposed to take a double input, so I validate that by trying to parse the input value to double like so :
return TextFormField(
decoration: InputDecoration(labelText: 'Price'),
keyboardType: TextInputType.number,
validator: (String value) {
double _parsedValue = double.tryParse(value);
if (_parsedValue == null) {
return "Please input a number";
}
},
onSaved: (String value) {
setState(() {
_price = double.parse(value);
});
},
);
Now that works as expected. However, if the user inputs for example 9,99 that would fail, because the parse expects 9.99 .
What I'm trying to do is, when the validator is called, I'd like to check the input string for any commas, and then if they are present, replace them with dots instead, and update the form value accordingly.
My question is - can we actually update the form state from within validators?
I think maybe what you need is a TextInputFormatter.
Here is a link to the docs https://docs.flutter.io/flutter/services/TextInputFormatter-class.html
There are pre-existing formatters you can use as a reference to convert comma's to dots.
I don't think you need to update the state in the validator. I would use only the save event to update the state. This way it gets very clear where the state is updated.
I believe nothing forbids you to update the state in the validate, but maybe it would get less organized. :)
Solution that do not exactly answer your question
I guess the best way to accomplish what you need would be using a TextInputFormatter with a WhitelistingTextInputFormatter, check it out:
Note the TextInputType.numberWithOptions(decimal: true) and that if the user pastes "-100,00" , it would become 100.0 - which for a price would be fine, but not for double values in general.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ValidatorState',
theme: ThemeData(primarySwatch: Colors.yellow),
home: MyFormPage(),
);
}
}
class MyFormPage extends StatefulWidget {
#override
_MyFormPageState createState() => _MyFormPageState();
}
class _MyFormPageState extends State<MyFormPage> {
final _formKey = GlobalKey<FormState>();
double _price;
void _save() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
Scaffold.of(_formKey.currentContext)
.showSnackBar(SnackBar(content: Text('New price defined! ($_price)')));
}
}
Widget _buildForm(BuildContext context) {
return Container(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter(RegExp("[0-9.]"))
],
decoration: InputDecoration(labelText: 'Price'),
keyboardType: TextInputType.numberWithOptions(decimal: true),
validator: (String value) {
double _parsedValue = double.tryParse(value);
if (_parsedValue == null) {
return "Please input a valid number";
}
if (_parsedValue == 0.0) {
return "Please input a valid price";
}
},
onSaved: (String value) {
setState(() {
_price = double.tryParse(value);
});
},
),
Text(""),
RaisedButton(
child: Text("Save"),
color: Theme.of(context).primaryColor,
textColor: Theme.of(context).primaryTextTheme.title.color,
onPressed: _save,
),
Text(""),
TextFormField(
decoration: InputDecoration(labelText: 'Copy and Paste area'),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Validator State"),
),
body: Form(
key:_formKey,
child: _buildForm(context),
),
);
}
}
Solution that answers your question
However, that is not exactly what you described. You want to automatically replace , to .. I would avoid doing that, as 1,234.56 would translate to 1.234.56, which is invalid. If you only strip out the commas, you end up with 1234.56 which is valid.
If you really want to do as you said, you have to use a TextEditingController and a function to normalize the text data. I've made the example below, check it out - specially the _priceController and the _parsePrice.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ValidatorState',
theme: ThemeData(primarySwatch: Colors.yellow),
home: MyFormPage(),
);
}
}
class MyFormPage extends StatefulWidget {
#override
_MyFormPageState createState() => _MyFormPageState();
}
class _MyFormPageState extends State<MyFormPage> {
final _formKey = GlobalKey<FormState>();
TextEditingController _priceController;
double _price;
#override
void initState() {
super.initState();
_priceController = TextEditingController();
}
#override
void dispose() {
_priceController?.dispose();
super.dispose();
}
void _save() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
Scaffold.of(_formKey.currentContext)
.showSnackBar(SnackBar(content: Text('New price defined! ($_price)')));
}
}
double _parsePrice(String text) {
var buffer = new StringBuffer();
text.runes.forEach((int rune) {
// acceptable runes are . or 0123456789
if (rune == 46 || (rune >= 48 && rune <= 57)) buffer.writeCharCode(rune);
// if we find a , we replace with a .
if (rune == 44) buffer.writeCharCode(46);
});
return double.tryParse(buffer.toString());
}
Widget _buildForm(BuildContext context) {
return Container(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormField(
controller: _priceController,
decoration: InputDecoration(labelText: 'Price'),
keyboardType: TextInputType.numberWithOptions(decimal: true),
validator: (String value) {
double _parsedValue = _parsePrice(value);
if (_parsedValue == null) {
return "Please input a valid number";
}
if (_parsedValue == 0.0) {
return "Please input a valid price";
}
},
onSaved: (String value) {
setState(() {
_price = _parsePrice(value);
_priceController.text = _price.toString();
});
},
),
Text(""),
RaisedButton(
child: Text("Save"),
color: Theme.of(context).primaryColor,
textColor: Theme.of(context).primaryTextTheme.title.color,
onPressed: _save,
),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Validator State"),
),
body: Form(
key:_formKey,
child: _buildForm(context),
),
);
}
}
hi did you get a fix for this?
I would rethink your strategy for this issue.
Maybe what you need is an observer function that is triggered when the user typing, which then looks at the comma and changes it to a dot.
TextFormField has a built in function,
onEditingCompleted and onFieldSubmitted which can run the function you have to make the check before the validate is run.
How to fix this For loop not working error? for loop only work once in Flutter
It's a simple login form. If username and password matched go to user
page else go to admin page.
method code:
checkLogin(){
setState(() {
for(var c=0;c < global.user_name_arr.length-1 ; c++){
if(global.user_name_arr[c]==myController.text&&global.user_password_arr[c]==myControllerPwd.text)
Navigator.push(context, MaterialPageRoute(builder: (context)=>user()),);
else
Navigator.push(context, MaterialPageRoute(builder:(context)=>admin()),); }
}); }
RaiseButton code:
new RaisedButton(
child:new Text("Click"),
onPressed:checkLogin,
)
global.dart
library user_login.globlas;
var user_name_arr=['bhanuka','isuru','sampath'];
var user_password_arr=['1234','123','12'];
First off, let's refactor your code :) Create a user class like so:
class User {
final String name;
final String password;
User(this.name, this.password);
}
Next, fix your global user collection:
final validUsers = [User('bhanuka', '1234'), User('isuru', '123'), User('sampath', '12')];
Now, use this code to perform correct navigation:
checkLogin() {
if (validUsers.indexWhere((user) => user.name == myController.text && user.password == myControllerPwd.text) >= 0) {
Navigator.push(context, MaterialPageRoute(builder: (context)=>user()),);
} else {
Navigator.push(context, MaterialPageRoute(builder:(context)=>admin()),);
}
}
There are better ways to do this comparison but I guess it's good enough for your use case.
here you are using if else so that condition is right or wrong one of the part is executed.
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(
title: 'Forms in Flutter',
home: new LoginPage(),
));
class LoginPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => new _LoginPageState();
}
class _LoginData {
String email = '';
String password = '';
}
class _LoginPageState extends State<LoginPage> {
final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
_LoginData _data = new _LoginData();
var user_name_arr = ['bhanuka', 'isuru', 'sampath'];
var user_password_arr = ['1234', '123', '12'];
var p;
void submit() {
if (this._formKey.currentState.validate()) {
_formKey.currentState.save(); // Save our form now.
if (user_name_arr.contains(_data.email)) {
p = user_name_arr.indexOf(_data.email);
if (user_password_arr.elementAt(p) == _data.password) {
Navigator.push(context, MaterialPageRoute(builder: (context)=>user()),);
} else {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => admin()),
);
}
} else {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => admin()),
);
}
}
}
#override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
return new Scaffold(
appBar: new AppBar(
title: new Text('Login'),
),
body: new Container(
padding: new EdgeInsets.all(20.0),
child: new Form(
key: this._formKey,
child: new ListView(
children: <Widget>[
new TextFormField(
keyboardType: TextInputType
.emailAddress, // Use email input type for emails.
decoration: new InputDecoration(
hintText: 'you#example.com',
labelText: 'E-mail Address'),
onSaved: (String value) {
this._data.email = value;
}),
new TextFormField(
obscureText: true, // Use secure text for passwords.
decoration: new InputDecoration(
hintText: 'Password', labelText: 'Enter your password'),
onSaved: (String value) {
this._data.password = value;
}),
new Container(
width: screenSize.width,
child: new RaisedButton(
child: new Text(
'Login',
style: new TextStyle(color: Colors.white),
),
onPressed: this.submit,
color: Colors.blue,
),
margin: new EdgeInsets.only(top: 20.0),
)
],
),
)),
);
}
}
class user extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(child: new Text("user")),
),
);
}
}
class admin extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(child: new Text("admin")),
),
);
}
}
Hai i am new in flutter/dart and hopefully you guys can help me on this. I am having this issue when i use obscureText: true and validator: in a TextFormField somehow i am unable to type anything in that field. Can someone tell me why is this?
class _LoginPageState extends State<LoginPage>{
final formKey = new GlobalKey<FormState>();
String _email;
String _password;
void validateAndSave(){
final form = formKey.currentState;
if (form.validate()){
print('Form is valid');
}else{
print('Form is invalid');
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Login'),
),
body: new Container(
padding: const EdgeInsets.all(20.0),
child: new Form(
key: formKey,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new TextFormField(
decoration: new InputDecoration(labelText: 'Email'),
validator: (value) => value.isEmpty ? 'Email can\'t be empty' : null,
onSaved: (value) => _email = value,
),
new TextFormField(
decoration: new InputDecoration(labelText: 'Password'),
obscureText: true,
validator: (value) => value.isEmpty ? 'Password can\'t be empty' : null,
onSaved: (value) => _password = value,
),
new RaisedButton(
child: new Text('Login', style: new TextStyle(fontSize: 20.0)),
onPressed: validateAndSave,
)
],
),
)
)
);
}
}
There is nothing wrong with the above code.
Anyway as I was testing the above code so Added/replaced few things like a validator class FieldValidator and instead of column use ListView etc.
Check out the code :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: LoginPage(),
);
}
}
class FieldValidator {
static String validateEmail(String value) {
if (value.isEmpty) return 'Email can\'t be empty!';
Pattern pattern =
r'^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regex = RegExp(pattern);
if (!regex.hasMatch(value)) {
return 'Enter Valid Email!';
}
return null;
}
static String validatePassword(String value) {
if (value.isEmpty) return 'Password can\'t be empty!';
if (value.length < 7) {
return 'Password must be more than 6 charater';
}
return null;
}
}
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
String _email;
String _password;
final _formKey = GlobalKey<FormState>();
void validateAndSave() {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
print('Form is valid $_email $_password');
} else {
print('Form is invalid');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
),
body: Container(
padding: const EdgeInsets.all(20.0),
child: Form(
key: _formKey,
child: ListView(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
validator: FieldValidator.validateEmail,
onSaved: (value) => _email = value.trim(),
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
validator: FieldValidator.validatePassword,
onSaved: (value) => _password = value.trim(),
),
RaisedButton(
child: Text('Login', style: TextStyle(fontSize: 20.0)),
onPressed: validateAndSave,
)
],
),
),
),
);
}
}
Hope it helps !
Assigning obscure text from a variable works for me.
obscureText: _obscurePasswordText
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;
}
}
}
I'm using floatingActionButton to increase TextForm Fields. i.e the fields increase by 1 once the button is tapped. The fields are actually increased on tap of the button but so confused on how to get values for each fields generated.
My problems:
When the user selects a value in the dropdown, all the values in the other generated dropdown fields changes to the new one. How do I solve this?
I'd like to add all the number value of the each of the generated Grade field together and also add the value of each of the generated Course Unit field together. i.e Add(sum) the value of all Grade fields the user generated.
See my full code below:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'My Grade Point',
theme: new ThemeData(primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _isLoading = false;
final formKey = new GlobalKey<FormState>();
final scaffoldKey = new GlobalKey<ScaffoldState>();
String _course;
String _grade;
String _unit;
String _mygp;
String _units;
String _totalgrade;
int counter = 1;
void _submit() {
final form = formKey.currentState;
if (form.validate()) {
setState(() => _totalgrade = _grade);
form.save();
}
}
Widget buildfields(int index) {
return new Column(
children: <Widget>[
new TextFormField(
onSaved: (String value) {
setState((){
_course = value;
});
},
validator: (val) {
return val.isEmpty
? "Enter Course Title $index"
: null;
},
decoration: new InputDecoration(labelText: "Course Title"),
),
new Row(
children: <Widget>[
new Expanded(
child: new TextFormField(
onSaved: (value) {
setState((){
_grade = value;
});
},
keyboardType: TextInputType.number,
decoration: new InputDecoration(labelText: "Grade"),
),
),
new Expanded(
child: new DropdownButton<String>(
onChanged: (String value) { setState((){
_unit = value;
});
},
hint: new Text('Course Unit'),
value: _unit,
items: <String>["1", "2", "3", "4", "5"].map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
),
),
],
),
],
);
}
#override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
var loginBtn = new RaisedButton(
onPressed: _submit,
child: new Text("CALCULATE"),
color: Colors.primaries[0],
);
var showForm = new Container(
padding: new EdgeInsets.all(20.0),
child: new Column(
children: <Widget>[
new Expanded(child: new Form(
key: formKey,
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return buildfields(index); },
itemCount: counter,
scrollDirection: Axis.vertical,
),
),
),
_isLoading ? new CircularProgressIndicator() : loginBtn
],
),
);
return new Scaffold(
appBar: new AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: new Text(_totalgrade.toString()),
),
body: showForm,
floatingActionButton: new FloatingActionButton(
onPressed: () {
setState(() {
counter++;
});
},
child: new Icon(Icons.add),
),
);
}
}
When the user selects a value in the dropdown, all the values in the other generated dropdown fields changes to the new one. How do I solve this?
The reason why DropdownButton children in ListView updates synchronously is because it fetches all its value from the _unit variable. I suggest using a List<Object> to manage the data of ListView items.
i.e.
class Course {
var title;
var grade;
var unit;
}
...
List<Course> _listCourse = [];
I'd like to add all the number value of the each of the generated Grade field together and also add the value of each of the generated Course Unit field together. i.e Add(sum) the value of all Grade fields the user generated.
With ListView data being managed in List<Course>, data inputted in the fields can be set in onChanged()
// Course Grade
TextFormField(
onChanged: (String value) {
setState(() {
_listCourse[index].grade = value;
});
},
)
and the values can be summed up with the help of a foreach loop.
int sumGrade = 0;
_listCourse.forEach((course) {
// Add up all Course Grade
sumGrade += num.tryParse(course.grade);
});
Here's a complete working sample based from the snippet you've shared.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'My Grade Point',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class Course {
var title;
var grade;
var unit;
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _isLoading = false;
final formKey = new GlobalKey<FormState>();
final scaffoldKey = new GlobalKey<ScaffoldState>();
String _course;
int _grade;
String _unit;
String _mygp;
String _units;
int _totalGrade;
int counter = 1;
List<Course> _listCourse = [];
#override
void initState() {
// Initialize empty List
_listCourse.add(Course());
super.initState();
}
void _submit() {
debugPrint('List Course Length: ${_listCourse.length}');
int sumGrade = 0;
_listCourse.forEach((course) {
debugPrint('Course Title: ${course.title}');
debugPrint('Course Grade: ${course.grade}');
// Add up all Course Grade
sumGrade += num.tryParse(course.grade);
debugPrint('Course Unit: ${course.unit}');
});
final form = formKey.currentState;
if (form.validate()) {
setState(() => _totalGrade = sumGrade);
form.save();
}
}
Widget buildField(int index) {
return new Column(
children: <Widget>[
new TextFormField(
onChanged: (String value) {
setState(() {
// _course = value;
_listCourse[index].title = value;
});
},
validator: (val) {
return val.isEmpty ? "Enter Course Title $index" : null;
},
decoration: new InputDecoration(labelText: "Course Title"),
),
new Row(
children: <Widget>[
new Expanded(
child: new TextFormField(
onChanged: (value) {
setState(() {
// _grade = value;
_listCourse[index].grade = value;
});
},
keyboardType: TextInputType.number,
decoration: new InputDecoration(labelText: "Grade"),
),
),
new Expanded(
child: new DropdownButton<String>(
onChanged: (String value) {
setState(() {
// _unit = value;
_listCourse[index].unit = value;
});
},
hint: new Text('Course Unit'),
value: _listCourse[index].unit,
items: <String>["1", "2", "3", "4", "5"].map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
),
),
],
),
],
);
}
#override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
var loginBtn = new RaisedButton(
onPressed: _submit,
child: new Text("CALCULATE"),
color: Colors.primaries[0],
);
var showForm = new Container(
padding: new EdgeInsets.all(20.0),
child: new Column(
children: <Widget>[
new Expanded(
child: new Form(
key: formKey,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return buildField(index);
},
itemCount: counter,
scrollDirection: Axis.vertical,
),
),
),
_isLoading ? new CircularProgressIndicator() : loginBtn
],
),
);
return new Scaffold(
appBar: new AppBar(
title: new Text(_totalGrade.toString()),
),
body: showForm,
floatingActionButton: new FloatingActionButton(
onPressed: () {
setState(() {
// Add an empty Course object on the List
_listCourse.add(Course());
counter++;
});
},
child: new Icon(Icons.add),
),
);
}
}