Flutter validation called on null in Login page - flutter

I'm trying to do my first app but I don't know how to use validator.
I tried to move all in myLogin class and i tried to split my code to find the problem.
I set formkey as global varible (with mail and pass variables).
import 'package:flutter/material.dart';
String _email, _password;
final formKey = new GlobalKey < FormState > ();
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
...
}
class myLogin extends StatefulWidget {
#override
_myLogin createState() => _myLogin();
}
class _myLogin extends State < myLogin > {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Title",
home: Scaffold(
body: (
Column(
children: [
titleRow,
],
)
)
),
);
}
}
This is my screen.
Widget titleRow = Container(
padding: const EdgeInsets.fromLTRB(40, 40, 40, 40),
child: Form(
key: formKey,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextFormField(
decoration: InputDecoration(labelText: "Email: "),
validator: (value) => !value.contains('#') ? "Not a valid Email" : null,
onSaved: (value) => _email = value
),
TextFormField(
decoration: InputDecoration(labelText: "Password: "),
validator: (value) => value.length < 8 ? "At least 8 character" : null,
onSaved: (value) => _password = value,
obscureText: true
),
OutlineButton(
child: new Text("Sign In"),
onPressed: funLogin(),
shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(30.0))
)
]
)
)
]
)
)
);
funLogin() {
final form = formKey.currentState;
if (form.validate()) {
form.save();
print(_email);
print(_password);
}
}
The Android emulator return error:
"NoSuchMethodError: The method validate was called on null"

You are using the function inside an onpressed so you should use it like this
onPressed : ()=> your function

Related

How to validate the TextFormField as we type in the input in Flutter

I have created a login screen with textformfield for email id and password using flutter. Also, I have added the validation to check these fields. The code is as below;
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
theme: ThemeData(
brightness: Brightness.dark,
),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var _formKey = GlobalKey<FormState>();
var isLoading = false;
void _submit() {
final isValid = _formKey.currentState.validate();
if (!isValid) {
return;
}
_formKey.currentState.save();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Form Validation"),
leading: Icon(Icons.filter_vintage),
),
//body
body: Padding(
padding: const EdgeInsets.all(16.0),
//form
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Text(
"Form-Validation In Flutter ",
style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold),
),
//styling
SizedBox(
height: MediaQuery.of(context).size.width * 0.1,
),
TextFormField(
decoration: InputDecoration(labelText: 'E-Mail'),
keyboardType: TextInputType.emailAddress,
onFieldSubmitted: (value) {
//Validator
},
validator: (value) {
if (value.isEmpty ||
!RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+")
.hasMatch(value)) {
return 'Enter a valid email!';
}
return null;
},
),
//box styling
SizedBox(
height: MediaQuery.of(context).size.width * 0.1,
),
//text input
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
keyboardType: TextInputType.emailAddress,
onFieldSubmitted: (value) {},
obscureText: true,
validator: (value) {
if (value.isEmpty) {
return 'Enter a valid password!';
}
return null;
},
),
SizedBox(
height: MediaQuery.of(context).size.width * 0.1,
),
RaisedButton(
padding: EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 15.0,
),
child: Text(
"Submit",
style: TextStyle(
fontSize: 24.0,
),
),
onPressed: () => _submit(),
)
],
),
),
),
);
}
}
The issue I am facing is, I want to validate the fields as soon as the user starts typing the input(dynamically) rather than clicking on the submit button to wait for the validation to happen. I did a lot of research yet could not find a solution. Thanks in advance for any help!
Flutter Form Validation with TextFormField
Here's an alternative implementation of the _TextSubmitWidgetState that uses a Form:
class _TextSubmitWidgetState extends State<TextSubmitForm> {
// declare a GlobalKey
final _formKey = GlobalKey<FormState>();
// declare a variable to keep track of the input text
String _name = '';
void _submit() {
// validate all the form fields
if (_formKey.currentState!.validate()) {
// on success, notify the parent widget
widget.onSubmit(_name);
}
}
#override
Widget build(BuildContext context) {
// build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'Enter your name',
),
// use the validator to return an error string (or null) based on the input text
validator: (text) {
if (text == null || text.isEmpty) {
return 'Can\'t be empty';
}
if (text.length < 4) {
return 'Too short';
}
return null;
},
// update the state variable when the text changes
onChanged: (text) => setState(() => _name = text),
),
ElevatedButton(
// only enable the button if the text is not empty
onPressed: _name.isNotEmpty ? _submit : null,
child: Text(
'Submit',
style: Theme.of(context).textTheme.headline6,
),
),
],
),
);
}
}
source : https://codewithandrea.com/articles/flutter-text-field-form-validation/
May be this can help someone. Inside the TextFormField use this line of code:
autovalidateMode: AutovalidateMode.onUserInteraction
use autovalidateMode in your Form widget
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: FormUI(),
),

how can I force user to enter the first letter is number 1

I have a textfield that should enter an ID, I need to force the user to enter the first number to be (1)
also, can anyone suggest how to learn RegExp package.. I find it solve most of this problems
import 'package:flutter/material.dart';
class TestDate extends StatelessWidget {
TestDate({Key? key}) : super(key: key);
var controller = TextEditingController();
final formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.all(40),
child: Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: controller,
decoration: InputDecoration(border: OutlineInputBorder()),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
},
child: const Text('Submit'),
),
],
),
),
),
);
}
}
It depends on what you really want.
Should the user have the freedom to write the text how he wants and you just want to validate at the end? Then update your validator callback:
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
} else if (!value.startsWith("1")) {
return 'Text needs to start with \'1\'';
}
return null;
},
However, if you want to force the user to always give a text which starts with 1, then you can create a class which extends TextInputFormatter:
class MyTextInputFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
if (!newValue.text.startsWith("1")) {
return oldValue;
}
return newValue;
}
}
Then:
TextFormField(
...
inputFormatters: [
MyTextInputFormatter()
],
),
By the way: If you don't need the controller, then don't instantiate one. If you do, then don't forget to dispose it.
Welcome. instead of forcing a user to enter number one(1). what you can do is show prefix widget on the front of textFormField and when the user submit the form you will pass the number one(1) value with you logic
class TestDate extends StatelessWidget {
TestDate({Key? key}) : super(key: key);
var controller = TextEditingController();
final formKey = GlobalKey<FormState>();
var idContainer= Container(
margin: const EdgeInsets.fromLTRB(0, 0, 10, 0),
width: 70,
decoration: BoxDecoration(
border: Border(
right: BorderSide(
width: 0.9,
color: Colors.grey,
),
),
),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'1', //1 or what ever you want
style: TextStyle(
fontSize: 2.3 * SizeConfig.heightMultiplier,
),
),
],
),
),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.all(40),
child: Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
prefixIcon: idContainer, //here you add prefix
controller: controller,
decoration: InputDecoration(border: OutlineInputBorder()),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
//here you can pass text value to fuction of what ever
//you want
String val = '1 + ${controller.text}';
sendData(val); //you can pass it to a function
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
},
child: const Text('Submit'),
),
],
),
),
),
);
}
}

How to initialise a type ValueChanged in Dart/Flutter?

I am new in Flutter, specially Flutter for Web. I am trying to reach something that's probably easy and basic, but I am facing difficulty.
This is my main.dart
Widget build(BuildContext context) {
bool loggedIn = false;
return MaterialApp(
home: loggedIn ? Navigator(
pages: [
MaterialPage(child: DashboardPage())
],
onPopPage: (route, result) => route.didPop(result),
) : LoginPage(didLoggedIn: (user) => print('Hello, ' + user) )
);
}
}
What I am trying to archive : if not logged in, go to the login screen. After the user successfully login, I'd like to execute a callback that will print hello (in fact I will set the state to logged in, but nevermind).
However I am facing difficulty to implement this callback, and maybe I am doing a wrong approach. This is the login page code:
import 'package:flutter/material.dart';
import 'package:email_validator/email_validator.dart';
class LoginPage extends StatefulWidget {
#override
LoginPageState createState() {
return LoginPageState();
}
}
class LoginPageState extends State<LoginPage> {
final username = TextEditingController();
final password = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool rememberMe = true;
//final ValueChanged didLoggedIn;
Widget _buildUsernameField() {
return TextFormField(
controller: username,
decoration: InputDecoration(labelText: 'Your E-Mail'),
validator: (value) {
if (value == null ||
value.isEmpty ||
!EmailValidator.validate(value)) {
return 'Invalid E-Mail';
}
return null;
});
}
Widget _buildPasswordField() {
return TextFormField(
controller: password,
obscureText: true,
decoration: InputDecoration(labelText: 'Password'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
});
}
#override
Widget build(BuildContext context) {
return Card(
child: Container(
color: Colors.white,
alignment: Alignment.center,
child: Container(
//color: Colors.green,
width: 600,
height: 300,
child: Column(
children: [
Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildUsernameField(),
_buildPasswordField(),
],
),
),
Column(
children: [
CheckboxListTile(title: Text('Remember me') ,
controlAffinity: ListTileControlAffinity.leading,
value: rememberMe, onChanged: (bool? value) {
setState(() {
rememberMe = value!;
});
}),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print(username.text + "/" + password.text);
//didLoggedIn(username.text);
}
},
child: Text('Log me in'),
),
Text('Forgot your password ?')
],
)
],
),
),
),
);
}
}
The problem is this line : final ValueChanged didLoggedIn;
It says that I need to initialise it. How to do that ?
And by the way, as I said, I am newbie, so maybe this could not be the best way to archive my goals, so if someone wants to give me a better solution, this will be more than welcomed.
Thanks !
You need to change 'LoginPage' like below.
move 'didLoggedIn' to 'LoginPage' not 'LoginPageState'.
make a constructor of 'LoginPage' to receive 'didLoggedIn'
access 'didLoggedIn' using 'widget.' prefix.
import 'package:flutter/material.dart';
import 'package:email_validator/email_validator.dart';
class LoginPage extends StatefulWidget {
final ValueChanged didLoggedIn;
LoginPage({required this.didLoggedIn});
#override
LoginPageState createState() {
return LoginPageState();
}
}
class LoginPageState extends State<LoginPage> {
final username = TextEditingController();
final password = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool rememberMe = true;
Widget _buildUsernameField() {
return TextFormField(
controller: username,
decoration: InputDecoration(labelText: 'Your E-Mail'),
validator: (value) {
if (value == null ||
value.isEmpty ||
!EmailValidator.validate(value)) {
return 'Invalid E-Mail';
}
return null;
});
}
Widget _buildPasswordField() {
return TextFormField(
controller: password,
obscureText: true,
decoration: InputDecoration(labelText: 'Password'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
});
}
#override
Widget build(BuildContext context) {
return Card(
child: Container(
color: Colors.white,
alignment: Alignment.center,
child: Container(
//color: Colors.green,
width: 600,
height: 300,
child: Column(
children: [
Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildUsernameField(),
_buildPasswordField(),
],
),
),
Column(
children: [
CheckboxListTile(title: Text('Remember me') ,
controlAffinity: ListTileControlAffinity.leading,
value: rememberMe, onChanged: (bool? value) {
setState(() {
rememberMe = value!;
});
}),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print(username.text + "/" + password.text);
//didLoggedIn(username.text);
widget.didLoggedIn(username.text);
}
},
child: Text('Log me in'),
),
Text('Forgot your password ?')
],
)
],
),
),
),
);
}
}
You need to create a constructor for LoginPage to save the callback on this class, then when you need to use the callback in LoginPageState you use the widget.callbackName() or widget.callbackName.call().

FormKey current state in flutter equals null

So i'm having a form like this:
Form(
key: formKey,
child: Column(
children: [
TextFormField(
validator: _validateForm,
cursorColor: Colors.black,
controller: _numberLocalsController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(
left: 15, bottom: 11, top: 11, right: 15),
hintText: "numero di locali"),
),
TextFormField(
validator: _validateForm,
cursorColor: Colors.black,
controller: _numberRoomsController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(
left: 15, bottom: 11, top: 11, right: 15),
hintText: "numero stanze da letto"),
),
TextFormField(
validator: _validateForm,
cursorColor: Colors.black,
keyboardType: TextInputType.number,
controller: _numberbathroomsController,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(
left: 15, bottom: 11, top: 11, right: 15),
hintText: "n° bagni"),
),
],
)),
and i initialized the formKey outside the build method like this :
class _FilterPageState extends State<FilterPage> {
final formKey = GlobalKey<FormState>();}
The idea is that there's a button that's clicked that just does the following :
final isValid = formKey.currentState.validate();
if (isValid) {
Navigator.pop(context, filterModel);
}
Now I get the error
Validate() was called on null
The formkey current context only has a value the first time i open the form. but when the navigator pops and i try to access the form again it gives the error.
Salma,
I'm still not clear on where / why you're seeing the null error, since I don't have access to your full code.
So I'll just post my own code sample below and you can compare implementations and hopefully that leads you to an answer.
From my test code below, it should be possible to Navigator.pop from the form page back to a previous page and then Navigator.push back to the form page and use the formKey again, without a null error.
FormStateTestPage
Just a base page with a Scaffold
BasePage
StatefulWidget where you can click a button to Navigator.push to the form page (FilterPage).
FilterPage
the Form is here
with the code sample below the Form is using a locally defined form key (localKey)
you can push to this FilterPage, pop back to BasePage, and push again to FilterPage and there should be no null errors
if you need access to form state outside of FilterPage, then you must declare a form key above FilterPage and provide it as a constructor argument
for outside access, replace the foreignKey: null with foreignKey: foreignKey (which is defined in _BasePageState). Then you can access form state from BasePage.
the code below is capturing the return value from the Navigator.push / Navigator.pop to FilterPage & back. That is how the form value is being shown in BasePage. The key line is: nukeCodes = await Navigator.push<String>
if nukeCodes is not null after popping back to BasePage, a setState is called, rebuilding BasePage & displaying the nukeCodes value.
class FormStateTestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Form State Test'),
),
body: BasePage(),
);
}
}
class BasePage extends StatefulWidget {
#override
_BasePageState createState() => _BasePageState();
}
class _BasePageState extends State<BasePage> {
Key foreignKey = GlobalKey<FormState>();
String nukeCodes;
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Nuke Codes:'),
Text(nukeCodes ?? 'awaiting'),
Center(
child: RaisedButton(
child: Text('Go to Form'),
onPressed: () async {
nukeCodes = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (context) => FilterPage(foreignKey: null)
) // pass null to have FilterPage use its own, self-defined formKey
// pass in foreignKey to use a formKey defined ABOVE FilterPage
);
if (nukeCodes != null && nukeCodes.isNotEmpty) {
print('Codes returned');
setState(() {});
}
}
),
),
],
);
}
}
class FilterPage extends StatefulWidget {
final GlobalKey<FormState> foreignKey;
FilterPage({this.foreignKey});
#override
_FilterPageState createState() => _FilterPageState();
}
class _FilterPageState extends State<FilterPage> {
final localKey = GlobalKey<FormState>();
GlobalKey<FormState> formKey;
#override
void initState() {
super.initState();
if (widget.foreignKey != null) {
formKey = widget.foreignKey;
print('Form using foreignKey');
}
else {
formKey = localKey;
print('Form using localKey');
}
}
#override
Widget build(BuildContext context) {
String codes;
return Scaffold(
body: SafeArea(
child: Form(
key: formKey,
//key: widget.formKey,
child: ListView(
children: [
TextFormField(
decoration: InputDecoration(
labelText: 'Enter Nuclear Codes'
),
onSaved: (val) => codes = val,
validator: (value) {
if (value.isEmpty) {
return 'A value is required';
}
return null;
},
),
RaisedButton(
child: Text('Submit'),
onPressed: () {
if (formKey.currentState.validate()) {
formKey.currentState.save();
Navigator.pop(context, codes);
}
},
)
],
),
),
),
);
}
}
Anyways, hopefully you can compare/contrast your code base with above and find out what's causing the null error.
Best of luck.

I have a Form where two text inputs are there. when user enter text in one input and goes to other the text vanishes . what to do?

I have a Form where two text inputs are there. when user enter text in one input and goes to other the text vanishes . what to do ?there are four dart files main.dart,new_transaction.dart,
the form module
import 'package:flutter/material.dart';
class NewTransaction extends StatelessWidget {
final titleController = TextEditingController();
final amountController=TextEditingController();
final Function addTx;
NewTransaction(this.addTx);
void submitData()
{
final enterTitle =titleController.text;
final enterAmount=double.parse(amountController.text);
if (enterTitle.isEmpty||enterAmount<=0)
{return;}
addTx(enterTitle,enterAmount);
}
#override
Widget build(BuildContext context) {
return
Card(
elevation: 7,
child: Container(
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'title'),
controller: titleController,
onSubmitted:(_)=>submitData(),
),
TextField(
decoration: InputDecoration(labelText: 'amount'),
controller: amountController,
keyboardType: TextInputType.number,
onSubmitted:(_)=>submitData(),
),
FlatButton(
onPressed: submitData,
child: Text('Add Transaction'),
textColor: Colors.purple,
),
],
),
));
}
}
i am calling this from Main like this
main.dart
Its because NewTransaction is StatelessWidget. When setState is called from its parent titleController and amountController will be recreated. So the value will be empty.
Solution:
Make NewTransaction as StatefulWidget.
Explanation:
StatefulWidget have state for them (Kind of separate runtime memory block storage to store values of the variables). So even if the parent widget rebuilds, this State of the StatefulWidget won't be recreated. It will be just reused with previous persisted values.
But StatelessWidget don't have State (Won't maintain values of the variable). So if parent widget get rebuilds, then this StatelessWidget also rebuild. which means all the variable like titleController and amountController will be deleted and recreated(with empty values).
Try this code.
class _NewTransactionState extends State<NewTransaction> {
String title = "";
int amount = 0;
void submitData() {
if (title == "" || amount <= 0) {
print("Invalid");
//TODO:Handle invalid inputs
return;
}
widget.addTx(title, amount);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Card(
elevation: 7,
child: Container(
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'title'),
onChanged: (value) {
title = value;
},
onSubmitted: (_) => submitData(),
),
TextField(
decoration: InputDecoration(labelText: 'amount'),
keyboardType: TextInputType.number,
onSubmitted: (_) => submitData(),
onChanged: (value) {
amount = int.parse(value);
},
),
FlatButton(
onPressed: submitData,
child: Text('Add Transaction'),
textColor: Colors.purple,
),
],
),
)),
),
);
}
}
Hope this will work for you if not then tell me know in comment.