I am creating a login form which has username and password field, i want to add validation when user skip any field. I have created this reusable textfield.
class RoundedInputField extends StatelessWidget {
final String hintText;
final ValueChanged<String> onChanged;
final TextEditingController controller;
final FormFieldValidator validate;
const RoundedInputField({Key key, this.hintText,
this.onChanged,
this.controller,this.validate,
})
: super(key: key);
#override
Widget build(BuildContext context) {
return TextFieldContainer(
child: TextFormField(
onChanged: onChanged,
controller: TextEditingController(),
validator: validate,
decoration: InputDecoration(
hintText: hintText,
border: InputBorder.none,
),
),
);
}
}
and calling it like this
RoundedInputField(hintText: "Username",icon: Icons.email,fontsize: 20,
controller: TextEditingController(text: user.username),
onChanged: (value){
user.username=value;
},
validate: (value){
if(value.isEmpty){
return "This field is required";
}
},
),
but validator property is not working properly, here it is.
please help if anyone know how to fix it!
I have been using TextFormField in a reusable way like this , it has served me for all the purposes i needed , i think it works for your case too
class BoxTextField extends StatelessWidget {
final TextEditingController controller;
final FormFieldValidator<String> validator;
final bool obsecure;
final bool readOnly;
final VoidCallback onTap;
final VoidCallback onEditingCompleted;
final TextInputType keyboardType;
final ValueChanged<String> onChanged;
final bool isMulti;
final bool autofocus;
final bool enabled;
final String errorText;
final String label;
final Widget suffix;
final Widget prefix;
BoxTextField(
{Key key,
this.controller,
this.validator,
this.keyboardType = TextInputType.text,
this.obsecure = false,
this.onTap,
this.isMulti = false,
this.readOnly = false,
this.autofocus = false,
this.errorText,
#required this.label,
this.suffix,
this.prefix,
this.enabled = true,
this.onEditingCompleted,
this.onChanged})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 4),
child: TextFormField(
onChanged: onChanged,
onEditingComplete: onEditingCompleted,
autofocus: autofocus,
minLines: isMulti ? 4 : 1,
maxLines: isMulti ? null : 1,
onTap: onTap,
enabled: enabled,
readOnly: readOnly,
obscureText: obsecure,
keyboardType: keyboardType,
controller: controller,
decoration: InputDecoration(
errorText: errorText,
prefixIcon: prefix,
suffixIcon: suffix,
labelStyle: TextStyle(fontSize: lableFontSize()),
labelText: label,
hintStyle: TextStyle(color: Colors.blueGrey, fontSize: 15),
contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 20),
enabledBorder: textFieldfocused(),
border: textFieldfocused(),
focusedBorder: textFieldfocused(),
errorBorder: errorrTextFieldBorder(),
focusedErrorBorder: errorrTextFieldBorder(),
),
validator: validator),
);
}
}
This is the Usage
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
TextEditingController _emailPhone = new TextEditingController();
TextEditingController _password = new TextEditingController();
GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
#override
void initState() {
super.initState();
}
String loginError;
bool loggingIn = false;
#override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildListDelegate([
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 50,
),
BoxTextField(
validator: (str) {
if (str.isEmpty) {
return tr('common.required');
}
return null;
},
controller: _emailPhone,
label: tr('login.username'),
),
SizedBox(
height: 10,
),
BoxTextField(
label: tr('login.password'),
controller: _password,
obsecure: true,
validator: (str) {
if (str.isEmpty) {
return tr('common.required');
}
return null;
},
),
Center(
child: BoxButton(
loading: loggingIn,
lable: tr('login.btn'),
onPressed: () {
},
)),
],
)),
)
]))
],
);
}
}
Replace your code and try this:
RoundedInputField(
hintText: "Username",
icon: Icons.email,
fontsize: 20,
controller: TextEditingController(text: user.username),
onChanged: (value) {
user.username = value;
},
validate: (value) {
if (value.isEmpty) {
return "This field is required";
}
return null;
},
),
Related
On focus, my textFormFields or textFields trigger unnecessary rebuilds, spike resource drains & make emulator & computer slow or even unusable. I've done considerable research on this. It seems like quite an issue & I haven't been able to resolve it.
What I've tried:
making form key a static final
=> static final GlobalKey<FormState> _signInFormKey = GlobalKey<FormState>();
using sizer instead of MediaQuery for dimensions
played around with calling field from ConsumerWidget, StatefulWidget, StatelessWidget, & ConsumerStatefulWidget
Nothing has worked so far.
Is there something I'm doing wrong?
Or is this the Flutter life of heated state management issues galore X 60hz refresh resource drain? :(
Here is the form field:
class CustomFormField extends StatefulWidget {
const CustomFormField({
Key? key,
required TextEditingController controller,
required bool prefixCurrencySymbol,
required String? currencySymbol,
required List<String> autoFillHints,
required bool blockSystemKeyboard,
required double width,
required FocusNode focusNode,
required TextInputType keyboardType,
required TextInputAction inputAction,
required String label,
required TextCapitalization textCapitalization,
required Function(String value) validator,
required bool obscurePasswordOption,
required bool saveAutofillData,
required bool passwordCreationField,
required Future<void> Function(String?)? onChanged,
}) : _formController = controller,
_prefixCurrencySymbol = prefixCurrencySymbol,
_currencySymbol = currencySymbol,
_autoFillHints = autoFillHints,
_blockSystemKeyboard = blockSystemKeyboard,
_width = width,
_formFocusNode = focusNode,
_keyboardType = keyboardType,
_inputAction = inputAction,
_textCapitalization = textCapitalization,
_label = label,
_validator = validator,
_obscurePasswordOption = obscurePasswordOption,
_saveAutofillData = saveAutofillData,
_passwordCreationField = passwordCreationField,
_onChanged = onChanged,
super(key: key);
final TextEditingController _formController;
final bool _prefixCurrencySymbol;
final String? _currencySymbol;
final List<String> _autoFillHints;
final bool _blockSystemKeyboard;
final double _width;
final FocusNode _formFocusNode;
final TextInputType _keyboardType;
final TextInputAction _inputAction;
final String _label;
final bool _obscurePasswordOption;
final TextCapitalization _textCapitalization;
final Function(String) _validator;
final bool _saveAutofillData;
final bool _passwordCreationField;
final Future<void> Function(String?)? _onChanged;
#override
State<CustomFormField> createState() => _CustomFormFieldState();
}
class _CustomFormFieldState extends State<CustomFormField> {
bool _obscurePassword = true;
#override
void dispose() {
widget._formFocusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget._width,
child: TextFormField(
style: ThemeEndpoints.textFieldTextStyle(),
autofillHints: widget._autoFillHints,
enableInteractiveSelection: true,
enableSuggestions: false,
autocorrect: false,
textAlignVertical: const TextAlignVertical(y: 0.1),
readOnly: widget._blockSystemKeyboard,
maxLines: 1,
controller: widget._formController,
focusNode: widget._formFocusNode,
keyboardType: widget._keyboardType,
obscureText: (widget._obscurePasswordOption) ? _obscurePassword : false,
textCapitalization: widget._textCapitalization,
textInputAction: widget._inputAction,
validator: (text) => widget._validator(text!),
onChanged: (text) => (widget._passwordCreationField && text.isNotEmpty)
? widget._onChanged!(text)
: null,
onEditingComplete:
(widget._saveAutofillData) ? () => TextInput.finishAutofillContext() : null,
toolbarOptions: const ToolbarOptions(
copy: true,
paste: true,
cut: true,
selectAll: true,
),
decoration: InputDecoration(
filled: true,
fillColor: ThemeEndpoints.textFieldBackgroundColor(),
alignLabelWithHint: true,
labelText: widget._label,
labelStyle: ThemeEndpoints.textFieldLabelStyle(),
contentPadding: const EdgeInsets.fromLTRB(32, 24, 12, 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
errorStyle: ThemeEndpoints.textFieldErrorTextStyle(),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: const BorderSide(
color: BrandColors.appLightRed,
width: 2,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: const BorderSide(
color: BrandColors.appRed,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: const BorderSide(
color: BrandColors.luckyLime,
width: 2,
),
),
prefixIconConstraints: (widget._prefixCurrencySymbol)
? const BoxConstraints(minWidth: 0, minHeight: 0)
: null,
prefixIcon: (widget._prefixCurrencySymbol)
? Container(
margin: const EdgeInsets.all(13),
child: Text(
widget._currencySymbol!,
style: ThemeEndpoints.textFieldCurrencySymbolTextStyle(),
),
)
: null,
suffixIcon: (widget._obscurePasswordOption)
? GestureDetector(
onTap: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
child: Container(
margin: const EdgeInsets.all(13),
child: _obscurePassword
? ThemeEndpoints.textFieldPasswordNotVisible()
: ThemeEndpoints.textFieldPasswordVisible(),
),
)
: null,
),
),
);
}
}
Here is my Form:
class SignInForm extends ConsumerWidget {
final FocusNode emailFocusNode;
final FocusNode passwordFocusNode;
SignInForm({
Key? key,
required this.emailFocusNode,
required this.passwordFocusNode,
}) : super(key: key);
final TextEditingController _email = TextEditingController();
final TextEditingController _password = TextEditingController();
static final GlobalKey<FormState> _signInFormKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context, WidgetRef ref) {
return AutofillGroup(
child: Form(
key: _signInFormKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
TextContent.of(context).authSignInText,
style: ThemeEndpoints.primaryHeader(),
),
const SizedBox(height: 16.0),
CustomFormField(
controller: _email,
onChanged: null,
prefixCurrencySymbol: false,
currencySymbol: null,
autoFillHints: const [AutofillHints.email],
blockSystemKeyboard: false,
width: 90.w,
focusNode: emailFocusNode,
keyboardType: TextInputType.emailAddress,
inputAction: TextInputAction.next,
label: TextContent.of(context).authFormEmailText,
textCapitalization: TextCapitalization.none,
validator: (email) => FormValidationUtility.emailFormValidator(
context,
email,
),
obscurePasswordOption: false,
saveAutofillData: false,
passwordCreationField: false,
),
const SizedBox(height: 16.0),
CustomFormField(
controller: _password,
onChanged: null,
prefixCurrencySymbol: false,
currencySymbol: null,
autoFillHints: const [AutofillHints.password],
blockSystemKeyboard: false,
width: 90.w,
focusNode: passwordFocusNode,
keyboardType: TextInputType.visiblePassword,
inputAction: TextInputAction.done,
label: TextContent.of(context).authFormPasswordText,
textCapitalization: TextCapitalization.none,
validator: (password) => FormValidationUtility.passwordGeneralValidator(
context,
password,
),
obscurePasswordOption: true,
saveAutofillData: false,
passwordCreationField: false,
),
const SizedBox(height: 64.0),
CustomButton(
buttonText: TextContent.of(context).authSignInText,
width: 50.w,
onPressed: () async => _onSignInPressed(
context,
ref,
[emailFocusNode, passwordFocusNode],
),
),
const SizedBox(height: 16.0),
const SignUpInkwell(),
const SizedBox(height: 16.0),
const SignInOptionsPulldown(),
],
),
),
);
}
// On pressed button execution
Future<void> _onSignInPressed(
BuildContext context,
WidgetRef ref,
List<FocusNode> focusNodes,
) async {
for (FocusNode node in focusNodes) {
node.unfocus();
}
if (_signInFormKey.currentState!.validate()) {
await ref.read(authenticationEndpoints).signIn(
context: context,
email: _email.text.trim(),
password: _password.text.trim(),
);
}
}
}
I just faced this issue last week, and found long discussion on github issue. but im forget to mark the issue, so i cant refer it.
there are 2 point that i get:
everytime focus triggered, the keyboard will appear on screen. this will trigger the screen to rebuild. Since numbers of the widget on screen was changed
there are behaviors in flutter, that when we have a nested StatefullWidget. here the question related: https://stackoverflow.com/a/50584602/12838877
in your case, you have parent statefullwidget, and some CustomTextField which is a statefull widget.
my workaround for my issue last week:
to avoid rebuild unnecessary widget every focus changed, i try to minimize use local method for all my widget. i choose to wrap into new StatelessWidget or StatefullWidget. This will not rebuild your entire widget tree.
then for the second problem, my solution is for every component that extends to StatefullWidget, i assign specific valueKey. even when i call setState and re-execute build method, since my valueKey is same, the widget will not rebuild.
like below
CustomFormField(
key: ValueKey('emailform'),
controller: _email,
onChanged: null,
....),
CustomFormField(
key: ValueKey('pswdForm'),
controller: pswd`,
onChanged: null,
....)
I am using a reusable widget and trying to pass a validation before the form is submitted.
But, I am not getting the error message that I should get. I have read the code several times, but do not find where is the problem. If you can have a quick look and tell me what I am missing it would be great.
import 'package:flutter/material.dart';
class TextFormFieldWidget extends StatefulWidget {
String hintText;
TextEditingController controller;
int maxLength;
int maxLines;
// Function onChanged;
TextInputAction? actionKeyboard;
String? parametersValidate;
String? defaultText;
String? labelText;
TextInputType? textInputType;
FocusNode? focusNode;
bool? obscureText;
Widget? prefixIcon;
Function onSubmitField;
Function? onFieldTap;
Function? functionValidate;
String? decoration;
String? validator;
//final dynamic Function(String?)? onChanged;
TextFormFieldWidget({
Key? key,
required this.hintText,
required this.controller,
required this.onSubmitField,
required this.maxLength,
required this.maxLines,
// required this.onChanged,
this.labelText,
this.validator,
this.parametersValidate,
this.textInputType,
this.onFieldTap,
this.defaultText,
this.actionKeyboard,
this.prefixIcon,
this.focusNode,
this.obscureText,
this.functionValidate,
this.decoration,
}) : super(key: key);
#override
_TextFormFieldWidgetState createState() => _TextFormFieldWidgetState();
}
class _TextFormFieldWidgetState extends State<TextFormFieldWidget> {
double bottomPaddingToError = 12;
#override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
primaryColor: Colors.black,
),
child: TextFormField(
cursorColor: Colors.black,
// obscureText: widget.obscureText,
keyboardType: widget.textInputType,
textInputAction: widget.actionKeyboard,
focusNode: widget.focusNode,
maxLength: widget.maxLength,
maxLines: widget.maxLines,
style: const TextStyle(
color: Colors.black,
fontSize: 16.0,
fontWeight: FontWeight.w200,
fontStyle: FontStyle.normal,
letterSpacing: 1.2,
),
initialValue: widget.defaultText,
decoration: InputDecoration(
prefixIcon: widget.prefixIcon,
hintText: widget.hintText,
// labelText: widget.decoration!,
// enabledBorder: const OutlineInputBorder(
// borderSide: BorderSide(color: Colors.black),
// ),
// focusedBorder: const OutlineInputBorder(
// borderSide: BorderSide(color: Colors.black),
// ),
hintStyle: const TextStyle(
color: Colors.grey,
fontSize: 14.0,
fontWeight: FontWeight.w300,
fontStyle: FontStyle.normal,
letterSpacing: 1.2,
),
contentPadding: EdgeInsets.only(
top: 12, bottom: bottomPaddingToError, left: 8.0, right: 8.0),
isDense: true,
errorStyle: const TextStyle(
color: Colors.red,
fontSize: 12.0,
fontWeight: FontWeight.w300,
fontStyle: FontStyle.normal,
letterSpacing: 1.2,
),
errorBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedErrorBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
),
controller: widget.controller,
validator: (value) {
if (widget.functionValidate != null) {
String resultValidate =
widget.functionValidate!(value, widget.parametersValidate);
if (resultValidate != null) {
return resultValidate;
}
}
return null;
},
onFieldSubmitted: (value) {
if (widget.onSubmitField != null) widget.onSubmitField();
},
onTap: () {
if (widget.onFieldTap != null) widget.onFieldTap!();
},
),
);
}
}
String? commonValidation(String value, String messageError) {
var required = requiredValidator(value, messageError);
if (required != null) {
return required;
}
return null;
}
String? requiredValidator(value, messageError) {
if (value.isEmpty) {
return messageError;
}
return null;
}
void changeFocus(
BuildContext context, FocusNode currentFocus, FocusNode nextFocus) {
currentFocus.unfocus();
FocusScope.of(context).requestFocus(nextFocus);
}
Widget
Widget _buildTextFormField(String myHintText,TextEditingController myController,
int length,int lines,String myContent) {
return TextFormFieldWidget(
hintText: myHintText,
decoration: 'Test 33',
controller: myController,
maxLength: length,
maxLines: lines,
onSubmitField: () {},
validator:checkFieldEmpty( myContent),
);
String? checkFieldEmpty(String? fieldContent) { //<-- add String? as a return type
if(fieldContent!.isEmpty) {
return 'Field empty => Please, change this.';
}
return null;
View
SingleChildScrollView(
child: Form(
key: _addFirstTaskFormKey,
child: Column(
children: [
_buildTextFormField('Task Name',taskNameController,100,3,taskNameController.text),
ElevatedButton(
onPressed: (){
if(_addFirstTaskFormKey.currentState!.validate()){
print ('validated');
//taskItems.taskName = taskNameController.text;
//taskItems.notes = notesController.text;
}
}, child: const Text('Press'),
),
I think you're relatively new to flutter, don't worry I got you,
FormValidators are used to validate multiple fields declared inside a Form Widget.
Form widget uses a global Form key
declared Like this,
final _formKey = GlobalKey<FormState>();
and fields
Like this,
child: Form(child:Column(children:[
TextFormField(controller:controller),
TextFormField(controller:controller),
],),),
so when you need to validate fields just call,
_formKey.validate();
in the case above you are making a CustomTextFormField
you don't necessarily need to make it a stateful widget,
use StateLess Widget instead,
class CustomTextFormField extends StateLessWidget {
final TextInputType? keyboardType;
final TextEditingController? controller;
final FormFieldValidator<String>? validator;
final ValueChanged<String?>? onSaved;
final Function(String)? onChanged;
final VoidCallback? onTap;
const CustomTextFormField(
this.keyboardType,
this.controller,
this.validator,
this.onSaved,
this.onChanged,
this.onTap,
);
#override
Widget build(BuildContext context) {
return TextFormField(
keyboardType: keyboardType,
validator: validator,
autovalidateMode: autoValidateMode,
onSaved: onSaved,
onChanged: onChanged,
);
}
and use it like this
CustomTextFormField(
validator: (String? val) {
if(val==null) {
return "Value cannot be empty";
}
return null;
},
),
add any other validation that you may want to apply
and on save button call
if(_formKey.currentState.validate()) {
//SaveData
}else {
print("Validation Error")
}
This may be the problem. You're using null values. Flutter has Sound null safety so this means you can't set variables to null. I'm really curious, does your code even run?
Solution:
Scrap your validator and read through the Flutter Docs. They'll walk you through building a Form with validation here
validator: (value) {
if (widget.functionValidate != null) {
String resultValidate =
widget.functionValidate!(value, widget.parametersValidate);
if (resultValidate != null) {
return resultValidate;
}
}
return null;
},
onFieldSubmitted: (value) {
if (widget.onSubmitField != null) widget.onSubmitField();
},
onTap: () {
if (widget.onFieldTap != null) widget.onFieldTap!();
},
),
);
}
So I have a registration form with some fields and 1 submit button.
I created the UI of the form by separating it into a widget named custom_text_form_field.dart because the structure is all the same, only some widgets are dynamic to change, so I can use it multiple times
Following the original tutorial straight from Flutter, the validation works fine as it should, but the problem is that the implemented logic is applied to all the fields in the form - which I don't want that.
I want, for example like this
validator: (value) {
if (forms == "name") {
if (max > 32 && value.isEmpty) {
return 'Enter valid value on Name';
}
} else if (forms == "email") {
if (value == null || value.isEmpty) {
return 'Enter valid value on Email';
}
} else if (forms == "phone") {
if (max > 12 || value.isEmpty) {
return 'Enter valid value on Phone';
}
}
}
How to pass dynamic if-else logic into Widgets parameter?
Is this possible?
Or should I not use a separate form widget?
Here's my code:
custom_text_form_field.dart
part of 'widgets.dart';
class CustomTextFormField extends StatelessWidget {
final String title;
final String hintText;
final TextInputType type;
final bool obscureText;
final TextEditingController controller;
const CustomTextFormField({
Key? key,
required this.title,
required this.type,
required this.hintText,
this.obscureText = false,
required this.controller,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: blackTextStyle.copyWith(
fontSize: 16,
fontWeight: semiBold,
),
),
SizedBox(height: 8),
TextFormField(
cursorColor: kBlackColor,
keyboardType: type,
obscureText: obscureText,
controller: controller,
decoration: InputDecoration(
hintText: hintText,
hintStyle: greyTextStyle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
defaultRadius,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(
defaultRadius,
),
borderSide: BorderSide(
color: kPrimaryColor,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
],
),
);
}
}
signup_page.dart
part of 'pages.dart';
class SignUp extends StatefulWidget {
SignUp({super.key});
#override
State<SignUp> createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
final TextEditingController nameController = TextEditingController(text: '');
final TextEditingController emailController = TextEditingController(text: '');
final TextEditingController phoneController = TextEditingController(text: '');
final TextEditingController passwordController =
TextEditingController(text: '');
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
AppBar header() {
return AppBar(
backgroundColor: kPrimaryColor,
centerTitle: true,
title: Text(
'Sign Up',
style: whiteTextStyle,
),
);
}
Widget body() {
return SafeArea(
child: ListView(
padding: EdgeInsets.all(margin16),
children: [
CustomTextFormField(
title: "Name",
type: TextInputType.name,
hintText: 'Your full name',
controller: phoneController,
),
CustomTextFormField(
title: "E-Mail",
type: TextInputType.emailAddress,
hintText: 'Your e-mail address',
controller: emailController,
),
CustomTextFormField(
title: "Mobile Number",
type: TextInputType.phone,
hintText: 'Your mobile number',
controller: phoneController,
),
CustomButton(
title: 'Sign Up',
margin: EdgeInsets.only(top: 32),
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('True')),
);
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("False")));
}
},
),
],
),
);
}
return Form(
key: _formKey,
child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: kBackgroundColor,
appBar: header(),
body: body(),
),
);
}
}
Thank you, please help or advice..
define one more property in customtextformfield as FormFieldValidator<String>? validator and pass dynamic validator to all the textfields.
modify your custom_text_form_field.dart like this.
part of 'widgets.dart';
class CustomTextFormField extends StatelessWidget {
final String title;
final String hintText;
final TextInputType type;
final bool obscureText;
final TextEditingController controller;
FormFieldValidator<String>? validator;
const CustomTextFormField({
Key? key,
required this.title,
required this.type,
required this.hintText,
this.obscureText = false,
required this.controller,
this.validator,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: blackTextStyle.copyWith(
fontSize: 16,
fontWeight: semiBold,
),
),
SizedBox(height: 8),
TextFormField(
cursorColor: kBlackColor,
keyboardType: type,
obscureText: obscureText,
controller: controller,
validator:validator,
decoration: InputDecoration(
hintText: hintText,
hintStyle: greyTextStyle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
defaultRadius,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(
defaultRadius,
),
borderSide: BorderSide(
color: kPrimaryColor,
),
),
),
validator: validator,
),
],
),
);
}
}
and then pass validators for every textfield individually like this.
CustomTextFormField(
title: "Mobile Number",
type: TextInputType.phone,
hintText: 'Your mobile number',
controller: phoneController,
validator: (value) {
if (forms == "name") {
if (max > 32 && value.isEmpty) {
return 'Enter valid value on Name';
}
} else if (forms == "email") {
if (value == null || value.isEmpty) {
return 'Enter valid value on Email';
}
} else if (forms == "phone") {
if (max > 12 || value.isEmpty) {
return 'Enter valid value on Phone';
}
}
}
),
I have 2 widgets, one is the main one with a bunch of other widgets loaded on it. The other is the TextFormField that I need to validate.
Main widget:
RoundedInputField(
onChanged: (value) {
_email = value;
},
),
RoundedButton(
text: "LOGIN",
press: () async {}, // This is the button that should validate the input
);
Field widget:
class RoundedInputField extends StatefulWidget {
final String hintText;
final IconData icon;
final ValueChanged<String> onChanged;
const RoundedInputField({
Key key,
this.hintText,
this.icon = Icons.alternate_email,
this.onChanged,
}) : super(key: key);
#override
_RoundedInputFieldState createState() => _RoundedInputFieldState();
}
class _RoundedInputFieldState extends State<RoundedInputField> {
TextEditingController email = new TextEditingController();
#override
Widget build(BuildContext context) {
return TextFieldContainer(
child: TextFormField(
controller: email,
keyboardType: TextInputType.emailAddress, //TextInputType.phone,
onChanged: widget.onChanged,
decoration: InputDecoration(
hintText: "Your Email",
icon: Icon(
widget.icon,
color: Theme.of(context).primaryColorDark,
),
border: InputBorder.none,
),
validator: (value) {
return Helpers.CheckInput(value);
},
),
);
}
}
How can I validate the input clicking on the other LOGIN widget?
final _formKey = GlobalKey<FormState>();
...
Form(
key: _formKey,
child: Column(children: <Widget>[
RoundedInputField(
onChanged: (value) {
_email = value;
},
),
RoundedPasswordField(
onChanged: (value) {
_password = value;
},
),
RoundedButton(
text: "LOGIN",
press: () async {}
),
I have a custom StatefulWidget that when typing into an empty field, it will automatically add a new empty field below so the user can keep adding data.
However, as I use setState in onChanged to add the new field, the keyboard is dismissed and focus is lost on the currently focused field.
How can Io prevent this happening?
TextField(
hintText: widget.newEntryHint,
text: data[index].value,
onChanged: (val) {
setState(() {
data[index].value = val;
//if last item in list, add an extra field below
if (val.length > 0 && index == (data.length -1)) {
data.add(TextListItem(value: ""));
}
});
},
)
Custom TextField for reference:
class MyTextField extends StatefulWidget {
MyTextField({
this.text,
this.hintText = "",
this.onChanged,
this.onSubmitted,
this.textAlign = TextAlign.left,
this.focusNode,
this.autofocus = false,
this.obscureText = false,
this.padding = const EdgeInsets.all(0.0),
this.keyboardType = TextInputType.text,
this.canEdit = true,
this.isDarkMode = false,
this.textCapitalization = TextCapitalization.sentences,
this.key,
});
final String text;
final String hintText;
final ValueChanged<String> onChanged;
final ValueChanged<String> onSubmitted;
final TextAlign textAlign;
final FocusNode focusNode;
final bool autofocus;
final bool obscureText;
final EdgeInsets padding;
final TextInputType keyboardType;
final TextCapitalization textCapitalization;
final Key key;
final bool canEdit;
final isDarkMode;
#override
_MyTextFieldState createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
static const double textFieldPadding = 12.0;
TextEditingController editingController;
#override
void initState() {
super.initState();
editingController = TextEditingController(text: widget.text);
}
#override
Widget build(BuildContext context) {
return IgnorePointer(
ignoring: !widget.canEdit,
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(
top: textFieldPadding + widget.padding.top, bottom: textFieldPadding + widget.padding.bottom, left: widget.padding.left, right: widget.padding.right),
child: TextField(
key: widget.key,
maxLines: null,
textCapitalization: widget.textCapitalization,
keyboardType: widget.keyboardType,
keyboardAppearance: widget.isDarkMode ? Brightness.dark : Brightness.light,
controller: editingController,
onSubmitted: widget.onSubmitted,
onChanged: widget.onChanged,
style: TextStyle(
color: widget.isDarkMode ? Colors.white : MyColors.textBlue,
fontSize: 16.0,
fontWeight: FontWeight.w500),
autofocus: widget.autofocus,
focusNode: widget.focusNode,
textAlign: widget.textAlign,
obscureText: widget.obscureText,
decoration: InputDecoration(
hintText: widget.hintText,
hintStyle: TextStyle(
color: widget.isDarkMode ? MyColors.black[700] : MyColors.grey,
fontSize: 16.0,
fontWeight: FontWeight.w500),
border: InputBorder.none,
),
),
),
Divider(
color: widget.isDarkMode ? MyColors.black : MyColors.grey[150],
height: 1.0,
),
],
),
);
}
}
I have had the same problem. I have found that I use key: UniqueKey() in my container. On every setState it generates uniqueKey and updates the children. Check your keys.
In my case , that issue was about the key of TextFormField.
I created a formkey at outside of the page
final _formKey = GlobalKey<FormState>();
and than I gave that key to textformfield as a parameter
TextFormField(
focusNode: textFormFieldFocusNode,
key: _formKey,
....
....
I hope it creates a perspective to that kind problems.
I know it's been a while, maybe something was fixed since then, but I'm not seeing your issue anymore. At least with the way I'm setting up your widget (slight difference between your data.add() vs my fields.add()).
List<Widget> _fields;
ScrollController controller = new ScrollController();
_buildData() {
_fields = new List();
for (int i = 0; i < 3; i++) {
_fields.add(_createMyTextField('hello$i', i));
}
}
Widget _createMyTextField(String text, int index) {
return MyTextField(
text: text,
hintText: 'hello hint$index',
onChanged: (val) {
setState(() {
//if last item in list, add an extra field below
if (val.length > 0 && index == (_fields.length-1)) {
_fields.add(_createMyTextField("", index+1));
}
});
},
);
}
#override
void initState() {
super.initState();
_buildData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: ListView(
controller: controller,
children: _fields.toList()
)
);
}
I recently had the same issue. The problem in my case was caused by a Container in the tree having an optional margin that was controlled by the focus within the child tree. When a TextFormField was tapped the keyboard would pop up and down and the field did not have focus. However, the container's margin was correctly changed due to the focus.
By changing the container to have a margin of 0 instead of null, it fixed the problem. It may also apply to other properties like padding etc.