How to validate the TextFormField as we type in the input in Flutter - 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(),
),

Related

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'),
),
],
),
),
),
);
}
}

why the named parameter 'inputdataint' isn't defined and how to fix it in flutter

i am new to flutter. recently building a project of a timer app. ok the homepage is when the user open the app. it will enter a number for hours, minute and second they want to countdown. then this three numbers will be send to another page in a code line here
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: inputdataint),//inputdataint means the constructor i build. don't know correct or not
);
}
so the problem here is when I want to send the three numbers that input by the user and send it to the second screen that is the Countdowntimer screen it gives me an error of the inputdataint isn't defined. but I already defined the inputdataint in the class. can anyone help.
this is the error part
Navigator.push(context,MaterialPageRoute(builder: (context)=>CountDownTimer(inputdataint:hourController.text )));
and this is my whole code. i don't know whether is my code got problem or maybe ...
class inputdatacountdown extends StatefulWidget {
#override
_inputdatacountdownState createState() => _inputdatacountdownState();
}
class _inputdatacountdownState extends State<inputdatacountdown> {
final hourController = TextEditingController();
final minuteController = TextEditingController();
final secondController = TextEditingController();
final _key = GlobalKey<FormState>();
int inputdata;
_inputdatacountdownState(){
inputdata=int.parse(hourController.text);
}
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
final nametext =
MediaQuery.of(context).platformBrightness == Brightness.dark
? Colors.white
: Colors.black;
final nametextonbutton =
MediaQuery.of(context).platformBrightness == Brightness.dark
? Colors.black
: Colors.white;
return Scaffold(
body: Center(
child: Container(
child: Form(
key: _key,
child: Column(
children: [
TextFormField(
controller: hourController,
decoration: InputDecoration(
hintText: 'e.g 1',
labelText: 'hours',
border:
OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(Icons.close),
onPressed: () =>
hourController
.clear(),
),
),
keyboardType:
TextInputType.number,
textInputAction:
TextInputAction.done,
validator: (value) {
if (value.isEmpty) {
return 'hours cannot be empty';
} else
return null;
}),
TextFormField(
controller: minuteController,
decoration: InputDecoration(
hintText: 'e.g 1',
labelText: 'minutes',
border:
OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(Icons.close),
onPressed: () =>
minuteController
.clear(),
),
),
keyboardType:
TextInputType.number,
textInputAction:
TextInputAction.done,
validator: (value) {
if (value.isEmpty) {
return 'minutes cannot be empty';
} else
return null;
}),
TextFormField(
controller: secondController,
decoration: InputDecoration(
hintText: 'e.g 1',
labelText: 'second',
border:
OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(Icons.close),
onPressed: () =>
secondController
.clear(),
),
),
keyboardType:
TextInputType.number,
textInputAction:
TextInputAction.done,
validator: (value) {
if (value.isEmpty) {
return 'second cannot be empty';
} else
return null;
}),
FlatButton(
color: nametext,
onPressed: () {
if (_key.currentState
.validate()) {
print(
'hour: ${hourController.text}');
print(
'minutes: ${minuteController.text}');
print(
'second: ${secondController.text}');
Navigator.push(context,MaterialPageRoute(builder: (context)=>CountDownTimer(inputdataint:hourController.text )));
}
},
child: Text(
'submit ',
style: TextStyle(
color:
nametextonbutton),
))
],
)),
),
),
);
}
}
class CountDownTimer extends StatefulWidget {
#override
_CountDownTimerState createState() => _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
AnimationController controller;
String get timerString {
Duration duration = controller.duration * controller.value;
return '${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}';
}
int inputdataint;
CountDownTimer(int inputdataint){
this.inputdataint = inputdataint;
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: inputdataint),//come back to you later
);
}
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
final nametext =
MediaQuery.of(context).platformBrightness == Brightness.dark
? Colors.white
: Colors.black;
final nametextonbutton =
MediaQuery.of(context).platformBrightness == Brightness.dark
? Colors.black
: Colors.white;
return Scaffold(
backgroundColor: Colors.white10,
body: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(children: <Widget>[
Padding(
padding: EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Align(
alignment: FractionalOffset.center,
child: AspectRatio(
aspectRatio: 1.0,
child: Stack(
children: <Widget>[
Positioned.fill(
child: CustomPaint(
painter: CustomTimerPainter(
animation: controller,
backgroundColor: Colors.white,
color: themeData.indicatorColor,
)),
),
Align(
alignment: FractionalOffset.center,
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
Text(
"Count Down Timer",
style: TextStyle(
fontSize: 20.0, color: nametext),
),
Text(
timerString,
style: TextStyle(
fontSize: 112.0, color: nametext),
),
],
),
),
],
),
),
),
),
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return FloatingActionButton.extended(
onPressed: () {
if (controller.isAnimating)
controller.stop();
else {
controller.reverse(
from: controller.value == 0.0
? 1.0
: controller.value);
}
},
icon: Icon(controller.isAnimating
? Icons.pause
: Icons.play_arrow),
label: Text(
controller.isAnimating ? "Pause" : "Play"));
}),
],
))
]);
}),
);
}
}
What's happening there is that you send an argument named inputdataint to your Widget CountDownTimer by doing so:
Navigator.push(context,MaterialPageRoute(builder: (context)=>CountDownTimer(inputdataint:hourController.text )));
You can see there that you send the value hourController.text to the CountDownTimer as an argument called inputdataint.
The error came from the definition of your CountDownTimer widget definition.
You didn't declare the argument inputdataint but you are trying to use this value.
For doing so, you need to update your code like so:
class CountDownTimer extends StatefulWidget {
final String inputdataint;
const CountDownTimer({this.inputdataint});
#override
_CountDownTimerState createState() => _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
// Your widget there...
// Use widget.inputdataint to use the data your passed to this widget.
}

How to shift focus to next custom textfield in Flutter?

As per: How to shift focus to next textfield in flutter?, I used FocusScope.of(context).nextFocus() to shift focus. But this doesn't work when you use a reusable textfield class. It only works when you directly use TextField class inside Column.
import 'package:flutter/material.dart';
void main() {
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final focus = FocusScope.of(context);
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
CustomTextField(
textInputAction: TextInputAction.next,
onEditingComplete: () => focus.nextFocus(),
),
const SizedBox(height: 10),
CustomTextField(
textInputAction: TextInputAction.done,
onEditingComplete: () => focus.unfocus(),
),
],
),
),
),
);
}
}
class CustomTextField extends StatelessWidget {
final TextInputAction textInputAction;
final VoidCallback onEditingComplete;
const CustomTextField({
this.textInputAction = TextInputAction.done,
this.onEditingComplete = _onEditingComplete,
});
static _onEditingComplete() {}
#override
Widget build(BuildContext context) {
return TextField(
textInputAction: textInputAction,
onEditingComplete: onEditingComplete,
);
}
}
In this code, if I click next in keyboard it will not shift focus to next textfield. Please help me with this.
That's because the context doesn't have anything it could grab the focus from. Replace your code with this:
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final focus = FocusScope.of(context);
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
CustomTextField(
textInputAction: TextInputAction.next,
onEditingComplete: () => focus.nextFocus(),
),
SizedBox(height: 10),
CustomTextField(
textInputAction: TextInputAction.done,
onEditingComplete: () => focus.unfocus(),
),
],
),
);
}
}
You need to wrap your fields in a form widget with a form key and use a TextFormField instead of textField widget. Set the action to TextInputAction.next and it should work! You can also use TextInput.done to trigger the validation.
Here a fully working exemple:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class LogInPage extends StatefulWidget {
LogInPage({Key key}) : super(key: key);
#override
_LogInPageState createState() => _LogInPageState();
}
class _LogInPageState extends State<LogInPage> {
final _formKey = new GlobalKey<FormState>();
bool isLoading = false;
String firstName;
String lastName;
String password;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
backgroundColor: Colors.black,
body: body(),
);
}
Widget body() {
return Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
showInput(
firstName,
TextInputType.name,
Icons.drive_file_rename_outline,
"FirstName",
TextInputAction.next,
onSaved: (value) => firstName = value.trim()),
showInput(lastName, TextInputType.name,
Icons.drive_file_rename_outline, "LastName", TextInputAction.next,
onSaved: (value) => lastName = value.trim()),
showInput(null, TextInputType.text, Icons.drive_file_rename_outline,
"Password", TextInputAction.done,
isPassword: true, onSaved: (value) => password = value),
Padding(
padding: EdgeInsets.symmetric(vertical: 10),
),
showSaveButton(),
],
),
);
}
Widget showInput(String initialValue, TextInputType textInputType,
IconData icon, String label, TextInputAction textInputAction,
{#required Function onSaved, bool isPassword = false}) {
return Padding(
padding: EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: new TextFormField(
style: TextStyle(color: Theme.of(context).primaryColorLight),
maxLines: 1,
initialValue: initialValue,
keyboardType: textInputType,
textInputAction: textInputAction,
autofocus: false,
obscureText: isPassword,
enableSuggestions: !isPassword,
autocorrect: !isPassword,
decoration: new InputDecoration(
fillColor: Theme.of(context).primaryColor,
hintText: label,
hintStyle: TextStyle(color: Theme.of(context).primaryColorDark),
filled: true,
contentPadding: new EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(12.0),
),
icon: new Icon(
icon,
color: Theme.of(context).primaryColorLight,
)),
validator: (value) {
return value.isEmpty && !isPassword
? "You didn't filled this field."
: null;
},
onSaved: onSaved,
onFieldSubmitted:
textInputAction == TextInputAction.done ? (value) => save() : null,
),
);
}
Widget showSaveButton() {
return RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(100))),
color: Theme.of(context).primaryColor,
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 25),
child: isLoading
? SizedBox(height: 17, width: 17, child: CircularProgressIndicator())
: Text(
"Sauvegarder",
style: TextStyle(color: Theme.of(context).primaryColorLight),
),
onPressed: save,
);
}
void save() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
//TODO
}
}
}
FocusNode textSecondFocusNode = new FocusNode();
TextFormField textFirst = new TextFormField(
onFieldSubmitted: (String value) {
FocusScope.of(context).requestFocus(textSecondFocusNode);
},
);
TextFormField textSecond = new TextFormField(
focusNode: textSecondFocusNode,
);
// render textFirst and textSecond where you want

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.

Flutter provider, Right way to use GlobalKey<FormState> in Provider

I'm new at Provider package. and Just making demo app for learning purpose.
Here is my code of simple Form Widget.
1) RegistrationPage (Where my app is start)
class RegistrationPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("Title"),
),
body: MultiProvider(providers: [
ChangeNotifierProvider<UserProfileProvider>.value(value: UserProfileProvider()),
ChangeNotifierProvider<RegiFormProvider>.value(value: RegiFormProvider()),
], child: AllRegistrationWidgets()),
);
}
}
class AllRegistrationWidgets extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SetProfilePicWidget(),
RegistrationForm(),
],
),
),
),
BottomSaveButtonWidget()
],
),
),
);
}
}
class BottomSaveButtonWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final _userPicProvider =
Provider.of<UserProfileProvider>(context, listen: false);
final _formProvider =
Provider.of<RegiFormProvider>(context, listen: false);
return SafeArea(
bottom: true,
child: Container(
margin: EdgeInsets.all(15),
child: FloatingActionButton.extended(
heroTag: 'saveform',
icon: null,
label: Text('SUBMIT',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
)),
onPressed: () {
print(_userPicProvider.strImageFileName);
_formProvider.globalFormKey.currentState.validate();
print(_formProvider.firstName);
print(_formProvider.lastName);
},
)),
);
}
}
2) RegistrationForm
class RegistrationForm extends StatefulWidget {
#override
_RegistrationFormState createState() => _RegistrationFormState();
}
class _RegistrationFormState extends State<RegistrationForm> {
TextEditingController _editingControllerFname;
TextEditingController _editingControllerLname;
#override
void initState() {
_editingControllerFname = TextEditingController();
_editingControllerLname = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
final formProvider = Provider.of<RegiFormProvider>(context);
return _setupOtherWidget(formProvider);
}
_setupOtherWidget(RegiFormProvider _formProvider) {
return Container(
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SizedBox(height: 20),
Text(
'Fields with (*) are required.',
style: TextStyle(fontStyle: FontStyle.italic),
textAlign: TextAlign.left,
),
SizedBox(height: 20),
_formSetup(_formProvider)
],
),
);
}
_formSetup(RegiFormProvider _formProvider) {
return Form(
key: _formProvider.globalFormKey,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: _editingControllerFname,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
labelText: "First Name *",
hintText: "First Name *",
),
onSaved: (value) {},
validator: (String value) =>
_formProvider.validateFirstName(value)),
SizedBox(height: 15),
TextFormField(
controller: _editingControllerLname,
textCapitalization: TextCapitalization.sentences,
validator: (String value) =>
_formProvider.validateLastName(value),
onSaved: (value) {},
decoration: InputDecoration(
labelText: "Last Name *",
hintText: "Last Name *",
),
)
],
),
),
);
}
#override
void dispose() {
_editingControllerFname.dispose();
_editingControllerLname.dispose();
super.dispose();
}
}
3) RegiFormProvider
class RegiFormProvider with ChangeNotifier {
final GlobalKey<FormState> globalFormKey = GlobalKey<FormState>();
String _strFirstName;
String _strLasttName;
String get firstName => _strFirstName;
String get lastName => _strLasttName;
String validateFirstName(String value) {
if (value.trim().length == 0)
return 'Please enter first name';
else {
_strFirstName = value;
return null;
}
}
String validateLastName(String value) {
if (value.trim().length == 0)
return 'Please enter last name';
else {
_strLasttName = value;
return null;
}
}
}
Here you can see, RegiFormProvider is my first page where other is children widgets in widget tree. I'm using final GlobalKey<FormState> globalFormKey = GlobalKey<FormState>(); in the RegiFormProvider provider, Because I want to access this in the 1st RegistrationPage to check my firstName and lastName is valid or not.
I'm using a builder widget to get form level context like below , and then easily we can get the form instance by using that context. by this way we don't need global key anymore.
Form(
child: Builder(
builder: (ctx) {
return ListView(
padding: EdgeInsets.all(12),
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: "Title"),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
initialValue: formProduct.title,
validator: validateTitle,
onSaved: (value) {
formProduct.title = value;
},
),
TextFormField(
decoration: InputDecoration(labelText: "Price"),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
initialValue: formProduct.price == null
? ""
: formProduct.price.toString(),
keyboardType: TextInputType.number,
validator: validatePrice,
onSaved: (value) {
formProduct.price = double.parse(value);
},
),
TextFormField(
decoration: InputDecoration(labelText: "Description"),
textInputAction: TextInputAction.next,
initialValue: formProduct.description,
maxLines: 3,
validator: validateDescription,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
onSaved: (value) {
formProduct.description = value;
},
),
TextFormField(
decoration: InputDecoration(labelText: "Image Url"),
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => FocusScope.of(context).unfocus(),
initialValue: formProduct.imageUrl,
validator: validateImageUrl,
onSaved: (value) {
formProduct.imageUrl = value;
},
),
Padding(
padding: EdgeInsets.all(10),
child: FlatButton(
color: Colors.amberAccent,
onPressed: () {
if (Form.of(ctx).validate()) {
Form.of(ctx).save();
formProduct.id =
Random.secure().nextDouble().toString();
ProductsProvider provider =
Provider.of<ProductsProvider>(context,
listen: false);
editing
? provider.setProduct(formProduct)
: provider.addProduct(formProduct);
Router.back(context);
}
},
child: Text("Save"),
),
)
],
);
},
),
)
you can see the Form.of(ctx) gives us the current level form.