how to make a button disable in flutter - flutter

I'am trying to create a profile form . I want to disable or enable the button based on the validation. I tried to check the currentstate of form validated or not. it is working fine but when i type on one of the textFormField , I am getting error message on all of the textFormField .
Here is the code i'am working.
import 'dart:html';
import 'package:flutter/material.dart';
class ProfileForm extends StatefulWidget {
const ProfileForm({Key? key}) : super(key: key);
#override
State<ProfileForm> createState() => _ProfileFormState();
}
class _ProfileFormState extends State<ProfileForm> {
final _formKey = GlobalKey<FormState>();
DateTime selectedDate = DateTime.now();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _dobController = TextEditingController();
final _currentLocationController = TextEditingController();
final _locationIndiaController = TextEditingController();
bool btndisable = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
RegExp emailValidator = RegExp(
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,}))$');
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
onChanged: () {
notdisbled();
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Let’s create a profile for you",
style: AppTextStyleSecondary.headline2sec,
),
const SizedBox(
height: 12,
),
Padding(
padding: const EdgeInsets.only(right: 100.0),
child: Text(
"Please enter your personal details",
),
),
const SizedBox(
height: 16,
),
Text("Your name"),
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
onChanged: (val) {
},
validator: (val) {
if (val.toString().length < 2) {
return "Please enter your name";
}
return null;
},
controller: _nameController,
decoration: const InputDecoration(
isDense: true,
contentPadding:
EdgeInsets.symmetric(horizontal: 10, vertical: 12)),
),
Text(labelText: "Date of Birth"),
InkWell(
child: TextFormField(
onTap: () {
_selectDate(context);
},
validator: (val) {
if (val.toString().isEmpty) {
return "This field is required";
} else {
return null;
}
},
readOnly: true,
controller: _dobController,
),
),
Text("Email address"),
TextFormField(
controller: _emailController,
onChanged: (val) {},
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (val) {
String email = val!.trim();
if (emailValidator.hasMatch(email)) {
return null;
} else {
return "Enter valid email address";
}
},
decoration: const InputDecoration(
isDense: true,
contentPadding:
EdgeInsets.symmetric(horizontal: 10, vertical: 12)),
),
Text(labelText: "Your location where you currently live"),
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (val) {
if (val.toString().isEmpty) {
return "This field is required";
} else {
return null;
}
},
onChanged: (val) {
},
controller: _currentLocationController,
),
Text("Your location in India"),
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (val) {
if (val.toString().isEmpty) {
return "This field is required";
} else {
return null;
}
},
onChanged: (val) {},
controller: _locationIndiaController,
),
const SizedBox(
height: 25,
),
Container(
width: double.infinity,
height: 45,
decoration: BoxDecoration(
color: btndisable ? Colors.blue : Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: TextButton(
onPressed: (){
if(_formKey.currentState!.validate()){
Navigator.of(context).pushNamedAndRemoveUntil(
'HomeScreen', (Route<dynamic> route) => false);
}
}
child: Text("Continue"),
),
),
const SizedBox(
height: 20,
),
],
),
),
)),
);
}
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(1970),
lastDate: DateTime(2101));
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = picked;
_dobController.text =
"${selectedDate.day}/${selectedDate.month}/${selectedDate.year}";
});
}
}
void notdisbled() {
if (_formKey.currentState!.validate() == false) {
setState(() {
btndisable = false;
});
} else {
setState(() {
btndisable = true;
});
}
}
}

for disable the button change to this :
child: TextButton(
onPressed: _formKey.currentState != null && _formKey.currentState.validate() ? () {
Navigator.of(context).pushNamedAndRemoveUntil(
'HomeScreen', (Route<dynamic> route) => false);
}: null,
child: AppText(
text: "Continue",
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColor.white),
),
but when you use
formKey.currentState!.validate()
when you type any thing, flutter try call it every time so your form widget run all validator func in all its textfield. so it is natural for all textfield to get error message

Related

Not able to remove focus from input field

I have four textfields, a title field, a details field, a date field, and a time field. Both the date and time fields are wrapped within a gesture detector, and onTap calls a pickDateAndTime method. The problem is that when I click on the date field and try to manually change the time through the input rather than the dial way, the focus goes to the title field and when I am still on the time picker and type something in the time picker, the title field gets changed with the new input. The weird part is that this error just appeared out of nowhere, and there are no errors reported in the console.
class TodoScreen extends StatefulWidget {
final int? todoIndex;
final int? arrayIndex;
const TodoScreen({Key? key, this.todoIndex, this.arrayIndex})
: super(key: key);
#override
State<TodoScreen> createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final ArrayController arrayController = Get.find();
final AuthController authController = Get.find();
final String uid = Get.find<AuthController>().user!.uid;
late TextEditingController _dateController;
late TextEditingController _timeController;
late TextEditingController titleEditingController;
late TextEditingController detailEditingController;
late String _setTime, _setDate;
late String _hour, _minute, _time;
late String dateTime;
late bool done;
#override
void initState() {
super.initState();
String title = '';
String detail = '';
String date = '';
String? time = '';
if (widget.todoIndex != null) {
title = arrayController
.arrays[widget.arrayIndex!].todos![widget.todoIndex!].title ??
'';
detail = arrayController
.arrays[widget.arrayIndex!].todos![widget.todoIndex!].details ??
'';
date = arrayController
.arrays[widget.arrayIndex!].todos![widget.todoIndex!].date!;
time = arrayController
.arrays[widget.arrayIndex!].todos![widget.todoIndex!].time;
}
_dateController = TextEditingController(text: date);
_timeController = TextEditingController(text: time);
titleEditingController = TextEditingController(text: title);
detailEditingController = TextEditingController(text: detail);
done = (widget.todoIndex == null)
? false
: arrayController
.arrays[widget.arrayIndex!].todos![widget.todoIndex!].done!;
}
DateTime selectedDate = DateTime.now();
TimeOfDay selectedTime = TimeOfDay(
hour: (TimeOfDay.now().minute > 55)
? TimeOfDay.now().hour + 1
: TimeOfDay.now().hour,
minute: (TimeOfDay.now().minute > 55) ? 0 : TimeOfDay.now().minute + 5);
Future<DateTime?> _selectDate() => showDatePicker(
builder: (context, child) {
return datePickerTheme(child);
},
initialEntryMode: DatePickerEntryMode.calendarOnly,
context: context,
initialDate: selectedDate,
initialDatePickerMode: DatePickerMode.day,
firstDate: DateTime.now(),
lastDate: DateTime(DateTime.now().year + 5));
Future<TimeOfDay?> _selectTime() => showTimePicker(
builder: (context, child) {
return timePickerTheme(child);
},
context: context,
initialTime: selectedTime,
initialEntryMode: TimePickerEntryMode.input);
Future _pickDateTime() async {
DateTime? date = await _selectDate();
if (date == null) return;
if (date != null) {
selectedDate = date;
_dateController.text = DateFormat("MM/dd/yyyy").format(selectedDate);
}
TimeOfDay? time = await _selectTime();
if (time == null) {
_timeController.text = formatDate(
DateTime(
DateTime.now().year,
DateTime.now().day,
DateTime.now().month,
DateTime.now().hour,
DateTime.now().minute + 5),
[hh, ':', nn, " ", am]).toString();
}
if (time != null) {
selectedTime = time;
_hour = selectedTime.hour.toString();
_minute = selectedTime.minute.toString();
_time = '$_hour : $_minute';
_timeController.text = _time;
_timeController.text = formatDate(
DateTime(2019, 08, 1, selectedTime.hour, selectedTime.minute),
[hh, ':', nn, " ", am]).toString();
}
}
#override
Widget build(BuildContext context) {
bool visible =
(_dateController.text.isEmpty && _timeController.text.isEmpty)
? false
: true;
final formKey = GlobalKey<FormState>();
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text((widget.todoIndex == null) ? 'New Task' : 'Edit Task',
style: menuTextStyle),
leadingWidth: (MediaQuery.of(context).size.width < 768) ? 90.0 : 100.0,
leading: Center(
child: Padding(
padding: (MediaQuery.of(context).size.width < 768)
? const EdgeInsets.only(left: 0)
: const EdgeInsets.only(left: 21.0),
child: TextButton(
style: const ButtonStyle(
splashFactory: NoSplash.splashFactory,
),
onPressed: () {
Get.back();
},
child: Text(
"Cancel",
style: paragraphPrimary,
),
),
),
),
centerTitle: true,
actions: [
Center(
child: Padding(
padding: (MediaQuery.of(context).size.width < 768)
? const EdgeInsets.only(left: 0)
: const EdgeInsets.only(right: 21.0),
child: TextButton(
style: const ButtonStyle(
splashFactory: NoSplash.splashFactory,
),
onPressed: () async {
},
child: Text((widget.todoIndex == null) ? 'Add' : 'Update',
style: paragraphPrimary),
),
),
)
],
),
body: SafeArea(
child: Container(
width: double.infinity,
padding: (MediaQuery.of(context).size.width < 768)
? const EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0)
: const EdgeInsets.symmetric(horizontal: 35.0, vertical: 15.0),
child: Column(
children: [
Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
validator: Validator.titleValidator,
controller: titleEditingController,
autofocus: true, // problem here
autocorrect: false,
cursorColor: Colors.grey,
maxLines: 1,
maxLength: 25,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
counterStyle: counterTextStyle,
hintStyle: hintTextStyle,
hintText: "Title",
border: InputBorder.none),
style: todoScreenStyle),
primaryDivider,
TextField(
controller: detailEditingController,
maxLines: null,
autocorrect: false,
cursorColor: Colors.grey,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
counterStyle: counterTextStyle,
hintStyle: hintTextStyle,
hintText: "Notes",
border: InputBorder.none),
style: todoScreenDetailsStyle),
],
),
),
Visibility(
visible: (widget.todoIndex != null) ? true : false,
child: GestureDetector(
onTap: () {},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Completed",
style: todoScreenStyle,
),
Transform.scale(
scale: 1.3,
child: Theme(
data: ThemeData(
unselectedWidgetColor: const Color.fromARGB(
255, 187, 187, 187)),
child: Checkbox(
shape: const CircleBorder(),
checkColor: Colors.white,
activeColor: primaryColor,
value: done,
side: Theme.of(context).checkboxTheme.side,
onChanged: (value) {
setState(() {
done = value!;
});
})),
)
],
),
),
),
GestureDetector(
onTap: () async {
await _pickDateTime();
setState(() {
visible = true;
});
},
child: Column(
children: [
Row(
children: [
Flexible(
child: TextField(
enabled: false,
controller: _dateController,
onChanged: (String val) {
_setDate = val;
},
decoration: InputDecoration(
hintText: "Date",
hintStyle: hintTextStyle,
border: InputBorder.none),
style: todoScreenStyle,
),
),
visible
? IconButton(
onPressed: () {
_dateController.clear();
_timeController.clear();
setState(() {});
},
icon: const Icon(
Icons.close,
color: Colors.white,
))
: Container()
],
),
primaryDivider,
TextField(
onChanged: (String val) {
_setTime = val;
},
enabled: false,
controller: _timeController,
decoration: InputDecoration(
hintText: "Time",
hintStyle: hintTextStyle,
border: InputBorder.none),
style: todoScreenStyle,
)
],
),
),
],
),
),
),
);
}
}
Should I open an issue on Github, as I had not made any changes to the code for it behave this way and also because there were no errors in the console
Here is the full code on Github
Update
Here is a reproducible example:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const TodoScreen(),
);
}
}
class TodoScreen extends StatefulWidget {
const TodoScreen({Key? key}) : super(key: key);
#override
State<TodoScreen> createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
late TextEditingController _dateController;
late TextEditingController _timeController;
late TextEditingController titleEditingController;
late TextEditingController detailEditingController;
late String _setTime, _setDate;
late String _hour, _minute, _time;
late String dateTime;
#override
void initState() {
super.initState();
String title = '';
String detail = '';
String date = '';
String? time = '';
_dateController = TextEditingController(text: date);
_timeController = TextEditingController(text: time);
titleEditingController = TextEditingController(text: title);
detailEditingController = TextEditingController(text: detail);
}
#override
void dispose() {
super.dispose();
titleEditingController.dispose();
detailEditingController.dispose();
_timeController.dispose();
_dateController.dispose();
}
Theme timePickerTheme(child) => Theme(
data: ThemeData.dark().copyWith(
timePickerTheme: TimePickerThemeData(
backgroundColor: const Color.fromARGB(255, 70, 70, 70),
dayPeriodTextColor: Colors.green,
hourMinuteTextColor: MaterialStateColor.resolveWith((states) =>
states.contains(MaterialState.selected)
? Colors.white
: Colors.white),
dialHandColor: Colors.green,
helpTextStyle: TextStyle(
fontSize: 12, fontWeight: FontWeight.bold, color: Colors.green),
dialTextColor: MaterialStateColor.resolveWith((states) =>
states.contains(MaterialState.selected)
? Colors.white
: Colors.white),
entryModeIconColor: Colors.green,
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
foregroundColor:
MaterialStateColor.resolveWith((states) => Colors.green)),
),
),
child: child!,
);
Theme datePickerTheme(child) => Theme(
data: ThemeData.dark().copyWith(
colorScheme: ColorScheme.dark(
surface: Colors.green,
secondary: Colors.green,
onPrimary: Colors.white,
onSurface: Colors.white,
primary: Colors.green,
)),
child: child!,
);
DateTime selectedDate = DateTime.now();
TimeOfDay selectedTime = TimeOfDay(
hour: (TimeOfDay.now().minute > 55)
? TimeOfDay.now().hour + 1
: TimeOfDay.now().hour,
minute: (TimeOfDay.now().minute > 55) ? 0 : TimeOfDay.now().minute + 5);
Future<DateTime?> _selectDate() => showDatePicker(
builder: (context, child) {
return datePickerTheme(child);
},
initialEntryMode: DatePickerEntryMode.calendarOnly,
context: context,
initialDate: selectedDate,
initialDatePickerMode: DatePickerMode.day,
firstDate: DateTime.now(),
lastDate: DateTime(DateTime.now().year + 5));
Future<TimeOfDay?> _selectTime() => showTimePicker(
builder: (context, child) {
return timePickerTheme(child);
},
context: context,
initialTime: selectedTime,
initialEntryMode: TimePickerEntryMode.input);
Future _pickDateTime() async {
DateTime? date = await _selectDate();
if (date == null) return;
if (date != null) {
selectedDate = date;
_dateController.text = selectedDate.toString();
}
TimeOfDay? time = await _selectTime();
if (time != null) {
selectedTime = time;
_hour = selectedTime.hour.toString();
_minute = selectedTime.minute.toString();
_time = '$_hour : $_minute';
_timeController.text = _time;
_timeController.text =
DateTime(2019, 08, 1, selectedTime.hour, selectedTime.minute)
.toString();
}
}
#override
Widget build(BuildContext context) {
bool visible =
(_dateController.text.isEmpty && _timeController.text.isEmpty)
? false
: true;
final formKey = GlobalKey<FormState>();
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
centerTitle: true,
),
body: SafeArea(
child: Container(
width: double.infinity,
padding: (MediaQuery.of(context).size.width < 768)
? const EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0)
: const EdgeInsets.symmetric(horizontal: 35.0, vertical: 15.0),
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14.0)),
padding: const EdgeInsets.symmetric(
horizontal: 24.0, vertical: 15.0),
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: titleEditingController,
autofocus: true,
autocorrect: false,
cursorColor: Colors.grey,
maxLines: 1,
maxLength: 25,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
hintText: "Title", border: InputBorder.none),
),
Divider(color: Colors.black),
TextField(
controller: detailEditingController,
maxLines: null,
autocorrect: false,
cursorColor: Colors.grey,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
hintText: "Notes", border: InputBorder.none),
),
],
),
)),
GestureDetector(
onTap: () async {
await _pickDateTime();
setState(() {
visible = true;
});
},
child: Container(
margin: const EdgeInsets.only(top: 20.0),
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 24.0, vertical: 15.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14.0)),
child: Column(
children: [
Row(
children: [
Flexible(
child: TextField(
enabled: false,
controller: _dateController,
onChanged: (String val) {
_setDate = val;
},
decoration: InputDecoration(
hintText: "Date", border: InputBorder.none),
),
),
visible
? IconButton(
onPressed: () {
_dateController.clear();
_timeController.clear();
setState(() {});
},
icon: const Icon(
Icons.close,
color: Colors.white,
))
: Container()
],
),
Divider(
color: Colors.blue,
),
TextField(
onChanged: (String val) {
_setTime = val;
},
enabled: false,
controller: _timeController,
decoration: InputDecoration(
hintText: "Enter", border: InputBorder.none),
)
],
)),
),
],
),
),
),
);
}
}
In your main.dart file, you should return something like this:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
// This allows closing keyboard when tapping outside of a text field
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus &&
currentFocus.focusedChild != null) {
FocusManager.instance.primaryFocus!.unfocus();
}
},
child: // your app's entry point,
);
}
}
Add a focusNode in your textField:
FocusNode focusNode = FocusNode();
TextField(
focusNode: focusNode,
);
And then in the gesture detector, add that following code to unselect the textfield input:
FocusScope.of(context).requestFocus(FocusNode());
simply wrap your Scaffold widget GestureDetector and add FocusScope.of(context).requestFocus(FocusNode()); it will automatically unfocused text field when you click anywhere on your screen
GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Scaffold()
)
You can use below code to remove focus in gesture detector event
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) {
currentFocus.unfocus();
}

How to disable Flutter DatePicker?

I have an issue with DatePicker in my application. Here's a simple TextFormField that I've created in my app which will open up DatePicker whenever the user taps on it.
This widget is a part of a form where I also have specified the GlobalKey and TextController for it. The rightmost calendar icon uses the suffixIcon property of InputDecoration and it changes to a clear icon whenever the user selects a date.
Here's the code for the above widget.
TextFormField(
onTap: () => _selectStartDate(context),
controller: _startDateTextController,
keyboardType: TextInputType.datetime,
readOnly: true,
decoration: InputDecoration(
suffixIcon: showClear ? IconButton(
icon: Icon(Icons.clear),
onPressed: _clearStartDate,
) : Icon(Icons.date_range),
labelText: 'Start Date',
labelStyle: TextStyle(
fontSize: AppDimensions.font26,
color: AppColors.paraColor
),
),
validator: (value){
if(value!.isEmpty) {
return 'Please enter a date';
}
},
),
My goal is to let the user pick on a date, and clear it should they choose to do so.
Here's the code for the _selectStartDate and _clearStartDate functions as well as the necessary controller and key. I'm using the intl package to format the date.
final _formKey = GlobalKey<FormState>();
TextEditingController _startDateTextController = TextEditingController();
DateTime selectedStartDate = DateTime.now();
bool showClear = false;
_selectStartDate(BuildContext context) async {
final DateTime? newStartDate = await showDatePicker(
context: context,
initialDate: selectedStartDate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
helpText: 'STARTING DATE'
);
if(newStartDate != null && newStartDate != selectedStartDate) {
setState(() {
selectedStartDate = newStartDate;
_startDateTextController.text = DateFormat.yMMMd().format(selectedStartDate);
showClear = true;
});
}
}
_clearStartDate() {
_startDateTextController.clear();
setState(() {
showClear = !showClear;
});
}
When i run the app, the DatePicker pops up and I'm able to select a date. The date is then shown on the TextFormField like the image below.
As you can see the clear icon is displayed. However, when i clicked on it, the DatePicker still popped up. And when i clicked on cancel on the DatePicker window, the TextFormField is cleared as expected.
Here's the complete code.
class BookingForm extends StatefulWidget {
const BookingForm({Key? key}) : super(key: key);
#override
_BookingFormState createState() => _BookingFormState();
}
class _BookingFormState extends State<BookingForm> {
final _formKey = GlobalKey<FormState>();
TextEditingController _startDateTextController = TextEditingController();
DateTime selectedStartDate = DateTime.now();
bool showClear = false;
_selectStartDate(BuildContext context) async {
final DateTime? newStartDate = await showDatePicker(
context: context,
initialDate: selectedStartDate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
helpText: 'STARTING DATE'
);
if(newStartDate != null && newStartDate != selectedStartDate) {
setState(() {
selectedStartDate = newStartDate;
_startDateTextController.text = DateFormat.yMMMd().format(selectedStartDate);
showClear = true;
});
}
}
_clearStartDate() {
_startDateTextController.clear();
setState(() {
showClear = !showClear;
});
}
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Container(
margin: EdgeInsets.only(left: AppDimensions.width20, right: AppDimensions.width20),
padding: EdgeInsets.all(AppDimensions.height20),
decoration: BoxDecoration(
color: Colors.white70,
borderRadius: BorderRadius.circular(AppDimensions.radius20),
boxShadow: [
BoxShadow(
color: Color(0xFFe8e8e8),
blurRadius: 5.0,
spreadRadius: 1.0,
offset: Offset(2,2)
),
]
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
TextFormField(
onTap: () => _selectStartDate(context),
controller: _startDateTextController,
keyboardType: TextInputType.datetime,
readOnly: true,
decoration: InputDecoration(
suffixIcon: showClear ? IconButton(
icon: Icon(Icons.clear),
onPressed: _clearStartDate,
) : Icon(Icons.date_range),
labelText: 'Start Date',
labelStyle: TextStyle(
fontSize: AppDimensions.font26,
color: AppColors.paraColor
),
),
validator: (value){
if(value!.isEmpty) {
return 'Please enter a date';
}
},
),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.only(
top: AppDimensions.height10,
bottom: AppDimensions.height10,
left: AppDimensions.width45,
right: AppDimensions.width45
),
primary: AppColors.mainColor2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppDimensions.radius20)
)
),
child: SmallText(
text: 'Search',
size: AppDimensions.font26,
color: Colors.white,
),
onPressed: (){
if(_formKey.currentState!.validate()) {
_formKey.currentState!.save();
}
}
),
],
),
),
);
}
}
I'm not sure why this happens. Any help is greatly appreciated. Thank you

How set form validation in widget flutter

I'm working on flutter project .I have a revision form validator that is not working as expected. When I leave the TextFormField empty the validator doesn't show me anything. I want to stay on the revision form until I enter the values.
thanks in advance
my code :
class Revision extends StatefulWidget {
}
class _RevisionState extends State<Revision> with TickerProviderStateMixin {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
RevisionApi revisionApi = RevisionApi();
TextEditingController _Kilometrage_revisionController =
TextEditingController();
_showAddDialog() async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.white,
title: Text("Ajouter un évènement"),
content: StatefulBuilder(builder: (
BuildContext context,
StateSetter setState,
) {
return SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Expanded(
child: DropdownButtonFormField(
decoration: InputDecoration(
hoverColor: Colors.white,
//contentPadding: EdgeInsets.only(left: 10, right: 15, top: 15),
labelText: 'Type',
alignLabelWithHint: true,
labelStyle: TextStyle(
color: kPrimaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
),
dropdownColor: Colors.white,
value: status,
items: <DropdownMenuItem>[
DropdownMenuItem(
// value: 'videnge',
value: 0,
child: InkWell(
child: Text('videnge'),
hoverColor: Colors.indigo,
),
),
DropdownMenuItem(
// value: 'visite technique',
value: 1,
child: Text('visite technique'),
),
DropdownMenuItem(
// value: 'assurance véhicule',
value: 2,
child: Text('assurance véhicule'),
),
DropdownMenuItem(
// value: 'autre',
value: 3,
child: Text('autre'),
),
],
onChanged: (value) {
setState(() {
status = value;
});
},
)),
]),
if (status == 1) visiTechniqueDropdown(),
]),
));
}),
actions: <Widget>[
TextButton(
child: Text(
"Enregistrer",
style: TextStyle(
color: Colors.red, fontWeight: FontWeight.bold),
),
onPressed: () {
if (status == null) return;
setState(() {
if (_events[_controller.selectedDay] != null) {
_events[_controller.selectedDay].add(status);
} else {
_events[_controller.selectedDay] = [status];
}
prefs.setString(
"events", json.encode(encodeMap(_events)));
status;
setRevision();
_KilometrageController.clear();
_eventController.clear();
_EmplacementController.clear();
_DateController.clear();
_repeat_revisionController.clear();
_revision_agenceController.clear();
_Kilometrage_revisionController.clear();
Navigator.of(context).pop();
// Navigator.pop(context);
});
},
),
new TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Retour'),
),
],
));
}
void setRevision() async {
print("hello");
if (_formKey.currentState.validate()) {
String kilometrage_pour_vidange = _KilometrageController.text;
String revision_type = status.toString();
String revision_title = _eventController.text;
String revision_location = _EmplacementController.text;
String revision_date = _DateController.text;
String repeat_revision = _repeat_revisionController.text;
String revision_agence = _revision_agenceController.text;
String kilometrage_revision = _Kilometrage_revisionController.text;
revisionApi
.setRevision(
revision_type,
revision_title,
revision_date,
revision_location,
kilometrage_pour_vidange,
repeat_revision,
revision_agence,
kilometrage_revision,
)
.then((data) {
if (data != null) {
Navigator.pop(context);
Navigator.of(context).pop();
Navigator.push(
context, MaterialPageRoute(builder: (context) => Revision()));
}
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(data)));
}).catchError((error) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.toString())));
});
setState(() {});
}
}
Widget visiTechniqueDropdown() {
return Column(mainAxisSize: MainAxisSize.min, children: [
Row(
children: [
Flexible(
child: TextFormField(
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
validator: (value) {
if (value.isEmpty) {
return 'Password is required';
}
return null;
},
controller: _DateController,
cursorColor: kPrimaryColor,
decoration: InputDecoration(
labelText: 'Date',
alignLabelWithHint: true,
labelStyle: TextStyle(
color: kPrimaryColor,
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
How i can set the validator correctly ?
This is for you. Thanks and enjoy
// Create a corresponding State class.
// This class holds data related to the form.
class MyFormState extends State<MyForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey<FormState>,
// not a GlobalKey<MyFormState>.
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
// The validator receives the text that the user has entered.
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
// sendData();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Processing Data')));
}
},
child: Text('Submit'),
),
),
],
),
);
}
}

Validator works for all fields except one in flutter

I have a screen to add a customer's details. Name, company, mobile no.,email,profile picture. I have added validator to all the fields. Validator is working for all the fields but not for one field, the top one(Name). I dont know why. When I place the field below all the fields then the validator works but I want to keep the field on top. I want Name field to contain some value.
add_person.dart
import 'dart:io';
import 'package:email_validator/email_validator.dart';
import 'package:vers2cts/models/customer_model.dart';
import 'package:vers2cts/models/language.dart';
import 'package:vers2cts/models/languages_widget.dart';
import 'package:vers2cts/models/languages_model.dart';
import 'package:vers2cts/screens/people_list.dart';
import 'package:vers2cts/services/db_service.dart';
import 'package:vers2cts/utils/form_helper.dart';
import 'search_contacts.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:image_picker/image_picker.dart';
class AddPerson extends StatefulWidget {
String uname;
final String appBarTitle;
final CustomerModel customer;
AddPerson(this.uname,this.customer,this.appBarTitle);
#override
State<StatefulWidget> createState() {
return AddPersonState(uname,customer,appBarTitle);
}
}
class AddPersonState extends State<AddPerson> {
GlobalKey<FormState> globalFormKey = GlobalKey<FormState>();
String uname,email,mobnum;
AddPersonState(this.uname,this.customer,this.appBarTitle);
bool engcheckbox=false;
String appBarTitle;
CustomerModel customer=new CustomerModel();
LanguagesModel langs=new LanguagesModel();
DBService dbService=new DBService();
bool showPassword = false;
DateTime _date=DateTime.now();
TextEditingController datefield=TextEditingController();
PickedFile _imageFile;
final ImagePicker _picker=ImagePicker();
TextEditingController custfNameController = TextEditingController();
TextEditingController custlNameController = TextEditingController();
TextEditingController custMobileNoController = TextEditingController();
TextEditingController custCompanyController = TextEditingController();
TextEditingController addrController = TextEditingController();
TextEditingController custEmailController = TextEditingController();
void getImage(ImageSource source) async{
final pickedFile=await _picker.getImage(
source:source);
setState(() {
_imageFile=pickedFile;
customer.cust_photo = _imageFile.path;
});
}
Future<Null> _selectDate(BuildContext context)async {
DateTime _datePicker = await showDatePicker(
context: context,
initialDate: _date,
firstDate: DateTime(1947),
lastDate: DateTime(2030),);
if (_datePicker != null && _datePicker != _date) {
setState(() {
_date = _datePicker;
String formattedDate = DateFormat('dd-MM-yyyy').format(_date);
datefield.text=formattedDate.toString();
print(datefield.text);
});
}
}
#override
Widget build(BuildContext context){
var selectedLanguages = languages.where((element) => element.selected);
TextStyle textStyle=Theme.of(context).textTheme.title;
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
var _minimumPadding = 5.0;
custfNameController.text = customer.first_name;
custlNameController.text = customer.last_name;
custMobileNoController.text = customer.mob_num;
custCompanyController.text=customer.org_name;
addrController.text=customer.addr;
custEmailController.text=customer.email_id;
return WillPopScope(
onWillPop: () {
moveToLastScreen();
},
child: Scaffold(
appBar: AppBar(
title: Text(appBarTitle),
elevation: 1,
actions: [
IconButton(
icon: Icon(
Icons.search,
),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => SearchContacts()));
},
),
],
),
body:Form(
key: globalFormKey,
child: Container(
padding: EdgeInsets.only(left: 16, top: 25, right: 16),
child:
GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: ListView(
children: [
ImageProfile(customer.cust_photo),
SizedBox(
height: 35,
),
buildTextField("Name",custfNameController,
(value) => updatefName(value),(value)=>checkfname(value)),
buildTextField("Mobile",custMobileNoController,
(value) => updateMobile(value),(value)=>checkmobile(value)),
buildTextField("Company",custCompanyController,
(value) => updateCompany(value),(value)=>checkempty(value)),
buildTextField("Email",custEmailController,
(value) => updateEmail(value),(value)=>checkmail(value)),
Text("Address"),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: addrController,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: const BorderSide(width: 2.0),)),
keyboardType: TextInputType.multiline,
minLines: 5,//Normal textInputField will be displayed
maxLines: 5, // when user presses enter it will adapt to it
onChanged: (value) {
this.customer.addr = value;
},
),
),
SizedBox(
height: height * 0.02,
),
Divider(),
SizedBox(
height: height * 0.02,
),
Row(
children: <Widget>[
Expanded(
child: Text("Show on Call",
style: textStyle,
)
),
SizedBox(
height: height * 0.02,
),
SizedBox(
height: 35,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
OutlineButton(
padding: EdgeInsets.symmetric(horizontal: 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
onPressed: () {},
child: Text("CANCEL",
style: TextStyle(
fontSize: 14,
letterSpacing: 2.2,
color: Colors.black)),
),
RaisedButton(
onPressed: () {
setState(() {
_saveCustomer();
});
},
//color: Colors.purpleAccent,
color: Theme.of(context).primaryColor,
padding: EdgeInsets.symmetric(horizontal: 50),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
child: Text(
"SAVE",
style: TextStyle(
fontSize: 14,
letterSpacing: 2.2,
color: Colors.white),
),
)
],
)
],
),
),
),
)));
}
Widget bottomSheet(){
return Container(
height: 100,
width: MediaQuery.of(context).size.width ,
margin: EdgeInsets.symmetric(
horizontal:20,
vertical:20,
),
child: Column(
children: <Widget>[
Text("Choose profile photo",
style: TextStyle(fontSize: 20.0),),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton.icon(
onPressed: (){
getImage(ImageSource.camera);
},
icon:Icon(Icons.camera,color: Theme.of(context).primaryColor,), label:Text("camera")),
FlatButton.icon(
onPressed: (){
getImage(ImageSource.gallery);
},
icon:Icon(Icons.photo_library), label:Text("Gallery"))
],
)
],
),
);
}
//This is for 1st 3 Textfields name,mobile,company
Widget buildTextField(String labelText,tController,Function onChanged,Function validator) {
return Padding(
padding: const EdgeInsets.only(bottom: 35.0),
child: TextFormField(
controller:tController,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black,
),
decoration: InputDecoration(
contentPadding: EdgeInsets.only(bottom: 3),
labelText: labelText,
labelStyle:TextStyle(),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
onChanged: (String value) {
return onChanged(value);
},
validator:(String value) {
return validator(value);
},
),
);
}
void moveToLastScreen() {
Navigator.pop(context, true);
}
bool validateAndSave() {
final form = globalFormKey.currentState;
if (form.validate()) {
form.save();
return true;
}
return false;
}
void _saveCustomer() async {
if (validateAndSave()) {
var result;
var res;
var mob = await dbService.checkcustMobno(mobnum,uname);
var mail = await dbService.checkcustEmail(email,uname);
if (mob != null) {
FormHelper.showAlertDialog(
context, 'Error',
'Customer with this mobile number already exists');
}
else if (mail != null) {
FormHelper.showAlertDialog(
context, 'Error',
'Customer with this email id already exists');
}else {
if (customer.cust_id != null) { // Case 1: Update operation
result = await dbService.updateCustomer(customer);
} else { // Case 2: Insert Operation
result = await dbService.insertCustomer(customer);
}
if (result != 0) { // Success
moveToLastScreen();
FormHelper.showAlertDialog(
context, 'Status', 'Customer Saved Successfully');
} else { // Failure
FormHelper.showAlertDialog(
context, 'Status', 'Problem Saving Customer');
}
languages.forEach((lang) async {
print("${lang.name} : ${lang.selected}");
if (lang.selected) {
LanguagesModel language = LanguagesModel(lang: lang.name);
print("customer id from lang");
print(customer.cust_id);
await dbService.insertLanguages(language);
}
});
}
}
}
String updatefName(String value) {
customer.first_name = custfNameController.text;
customer.cre_by=uname;
print(uname);
}
String updatelName(String value) {
if(value.isEmpty)
customer.last_name = " ";
else
customer.last_name = custlNameController.text;
}
String updateMobile(value) {
customer.mob_num = custMobileNoController.text;
mobnum=value;
}
String updateEmail(value) {
customer.email_id = custEmailController.text;
email=value;
}
String updateCompany(String value) {
customer.org_name = custCompanyController.text;
}
String checkfname(String value){
if(value.isEmpty)
{
return "First name can't be null";
}
return null;
}
String checkempty(String value){
print("checkempty called");
if(value.isEmpty)
{
return "Can't be null";
}
return null;
}
String checkmobile(String value) {
print("checkmobile called");
if(value.isEmpty)
{
return 'Please enter phone no.';
}
if(value.length<6)
{
return 'Please enter a valid phone no.';
}
return null;
}
String checkmail(String value){
print("checkmail called");
if(value.isEmpty)
{
return 'Please Enter E-mail address';
}
if (!EmailValidator.validate(value)) {
return 'Please enter a valid Email';
}
return null;
}
}
Replace the ListView (that the buildTextFields are its children) with a Column and wrap the Column with a SingleChildScrollView.

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.