How to give focus to DateTimeFormField on Flutter? - flutter

I have a form with TextFormFields. I can give focus to next field using Tab key as usual. But, it skips DateTimeFormField. How can I make it show the Date picker instead of skipping it when it is its turn?
Did some search but couldn't find anything near it.
Here is my code samples. I simply use TextFormField's and DateTimeFormField inside a Column. But there is no focus option for DateTimeFormField.
class FormDialog extends ConsumerWidget {
const FormDialog({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(
height: 12,
),
CustomTextField(
labelText: firstName,
textInputAction: TextInputAction.next,
),
const SizedBox(
height: 12,
),
CustomTextField(
labelText: lastName,
textInputAction: TextInputAction.next,
),
const SizedBox(
height: 12,
),
CustomDateField(
labelText: dateOfBirth,
),
const SizedBox(
height: 12,
),
CustomTextField.email(
labelText: parentEmail,
),
],
);
}
}
class CustomDateField extends StatelessWidget {
const CustomDateField({
Key? key,
this.labelText,
this.hintText,
}) : super(key: key);
final String? labelText;
final String? hintText;
#override
Widget build(BuildContext context) {
return DateTimeFormField(
mode: DateTimeFieldPickerMode.date,
dateFormat: DateFormat.yMd(),
decoration: InputDecoration(
labelText: labelText,
hintText: hintText,
border: const OutlineInputBorder(),
helperText: ' ',
),
);
}
}
class CustomTextField extends StatelessWidget {
const CustomTextField({
Key? key,
this.labelText,
this.icon,
this.textInputAction,
}) : super(key: key);
final String? labelText;
final TextFieldIcon? icon;
final TextInputAction? textInputAction;
#override
Widget build(BuildContext context) {
return TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: InputDecoration(
hintText: labelText,
border: const OutlineInputBorder(),
prefixIcon: icon,
helperText: ' ',
),
textInputAction: textInputAction,
);
}
factory CustomTextField.email({
String? errorMessage,
String? labelText,
}) =>
CustomTextField(
labelText: labelText ?? email,
icon: const TextFieldIcon.email(),
textInputAction: TextInputAction.next,
);
}

According to the package's Github repository, it is built upon FormField, creating and initializing a FocusNode and passing the focus node to the widget might resolve the issue. If not you might need to implement your own form field.

Related

Issue with textFormField/TextField triggering unnecessary rebuilds when focused

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 code a textformfield widget and add validator then I called it from another screen but validator doesn't work

I code a textformfield widget and add validator then I called it from another screen but validator doesn't work. how to add validator in widget and call it from another class?
Input Field widget(this is what I called from loginscreen)
import 'package:flutter/material.dart';
import 'constants.dart';
class InputField extends StatelessWidget {
final TextEditingController controller;
final IconData? icon;
final String? text;
final String? errorText;
final TextInputType textInputType;
final TextAlign textAlign;
final Function function;
const InputField({
Key? key,
required this.controller,
this.icon,
this.text,
required this.textInputType,
this.errorText,
required this.textAlign,
required this.function,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: TextFormField(
controller: controller,
keyboardType: textInputType,
textInputAction: TextInputAction.next,
validator: (value){
function(value);
},
textAlign: textAlign,
decoration: InputDecoration(
prefixIcon: Icon(icon,color: kPrimaryColor,),
contentPadding: const EdgeInsets.all(5),
hintStyle: TextStyle(
fontFamily: 'InriaSans',
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold
),
hintText: text,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),),
),
),
margin: EdgeInsets.symmetric(vertical: 5, horizontal: 40),
);
}
}
LOGIN SCREEN
I called input field in this login screen.
Widget _buildEmail(){
return InputField(
controller: emailController,
icon: Icons.email,
text: 'User email',
textAlign: TextAlign.left,
textInputType: TextInputType.emailAddress,
function: (){
return Validator.emailValidate(emailController.text);
},
);
}

"Invalid constant value" when trying to create a dynamic TextInput widget

I'm trying to create a new TextInput widget for customizing TextFormField but i can't customize labelText. I need to send in constructor labelText for my TextFormField and show this String.
class TextInput extends StatelessWidget {
final TextEditingController textControler;
final String textLabelHint;
const TextInput({Key? key, required this.textControler,required this.textLabelHint}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 10.0),
child: TextFormField(
controller: textControler,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: textLabelHint,
),
),
);
}
}
But I have problem:
labelText: textLabelHint, //Invalid constant value.
You ned to remove const from decoration: const InputDecoration(...), since textLabelHint isn't a const value:
class TextInput extends StatelessWidget {
final TextEditingController textControler;
final String textLabelHint;
const TextInput(
{Key? key, required this.textControler, required this.textLabelHint})
: super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 10.0),
child: TextFormField(
controller: textControler,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: textLabelHint,
),
),
);
}
}
This error is there because textLabelHint is a final class property (not const) that could change based on the constructor value. However, where you try to pass this value is InputDecoration which you have marked as const. Thus, the error indicates that.
To resolve the issue, remove the const keyword before the InputDecoration:
class TextInput extends StatelessWidget {
final TextEditingController textControler;
final String textLabelHint;
const TextInput({Key? key, required this.textControler,required this.textLabelHint}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 10.0),
child: TextFormField(
controller: textControler,
decoration: InputDecoration( // <-- Notice the removed const
border: OutlineInputBorder(),
labelText: textLabelHint,
),
),
);
Try this:
class TextInput extends StatelessWidget {
final TextEditingController textControler;
final String textLabelHint;
const TextInput(
{Key? key, required this.textControler, required this.textLabelHint})
: super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 10.0),
child: TextFormField(
controller: textControler,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: textLabelHint,
),
),
);
}
}

how to create resuable textfield with validator in flutter

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

Custom controller for TextFormField clears text entered when focus removed - Flutter

I have created a custom controller for TextFormField, but I use it in my widgets it clears the input upon releasing focus from the input. the same setup works fine with the local widget set up with the same but I want to have a common widget to use throughout the application for better code maintainability. How can have the input intact entered into the input field??
Custom Input Widget-
import 'package:HSSE/screens/constant.dart';
import 'package:flutter/material.dart';
class AppInput extends StatelessWidget {
const AppInput(
{Key key,
this.correct = false,
this.hintText,
this.inputLabel,
this.labelFont = 18.0,
this.labelFontWeight,
this.validators,
this.keyboard,
this.length,
this.icon,
this.trailingIcon,
this.readOnly = false,
this.inputController,
this.onTap = null,
this.onInputValueChange,
this.labelText})
: super(key: key);
final bool correct;
final String hintText;
final String labelText;
final String inputLabel;
final double labelFont;
final FontWeight labelFontWeight;
final Function validators;
final Function onInputValueChange;
final TextInputType keyboard;
final int length;
final Icon icon;
final Icon trailingIcon;
final bool readOnly;
final Function onTap;
final TextEditingController inputController;
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: Color(0Xffcacaca), width: 0.75),
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
labelText,
style: TextStyle(fontSize: labelFont),
),
TextFormField(
readOnly: readOnly,
keyboardType: keyboard,
maxLength: length,
controller: inputController,
cursorColor: apmt_color,
decoration: InputDecoration(
prefixIcon: icon,
suffixIcon: trailingIcon,
errorStyle: TextStyle(
fontSize: 16,
),
border: InputBorder.none,
hintText: hintText,
labelStyle: TextStyle(
color: Colors.black,
),
fillColor: Colors.red,
),
style: TextStyle(color: Colors.black),
onChanged: (value) {
onInputValueChange(value);
},
validator: (value) {
return validators(value);
},
)
],
),
);
}
}
When I use it into my other widget like below -
final TextEditingController emailCtrl = TextEditingController();
AppInput(
inputController: emailCtrl,
keyboard: TextInputType.emailAddress,
hintText: Localizer.of(context).translate('inspectorNameHint'),
labelText: Localizer.of(context).translate('inspectorName'),
validators: (String name) {
if (name.isEmpty)
return Localizer.of(context)
.translate('inspectorNameError');
},
)
the input gets clears when the focus gets removed from the input field it works fine with the same setup on the local widget but I want to make a common widget to optimize the code.