Issue with textFormField/TextField triggering unnecessary rebuilds when focused - flutter

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,
....)

Related

Flutter - GestureDetector function does not work with custom widget

I am trying to manage some gap between elements and update the gap using setState(). Unfortunately, it did not work with a custom widget TextFieldInput as child of GestureDetector.
GestureDetector(
onTap: () {
setState(() {
gap = 16.00;
});
},
child: TextFieldInput(
textEditingController: _passwordController,
hintText: 'Enter your password',
textInputType: TextInputType.text,
isPass: true,
),
),
But it did work with a Text widget.
GestureDetector(
onTap: () {
setState(() {
gap = smallGap;
});
},
child: Container(
child: Padding(
padding: EdgeInsets.all(5),
child: Text('Tap here'),
),
padding: const EdgeInsets.symmetric(
vertical: 8,
),
),
)
Here is the TextFieldInput widget structure:
import 'package:flutter/material.dart';
class TextFieldInput extends StatefulWidget {
final TextEditingController textEditingController;
final bool isPass;
final String hintText;
final TextInputType textInputType;
final VoidCallback onTap;
const TextFieldInput({
super.key,
required this.textEditingController,
this.isPass = false,
required this.hintText,
required this.textInputType,
required this.onTap,
this.child,
});
final Widget? child;
#override
State<TextFieldInput> createState() => _TextFieldInputState();
}
class _TextFieldInputState extends State<TextFieldInput> {
#override
Widget build(BuildContext context) {
final inputBorder = OutlineInputBorder(
borderSide: Divider.createBorderSide(context)
);
return TextField(
controller: widget.textEditingController,
decoration: InputDecoration(
hintText: widget.hintText,
border: inputBorder,
focusedBorder: inputBorder,
enabledBorder: inputBorder,
filled: true,
contentPadding: const EdgeInsets.all(8),
),
keyboardType: widget.textInputType,
obscureText: widget.isPass,
onTap: () {},
);
}
}
But "gap" does not change even on tapping on the TextFieldInput.
Can anyone help with this ?
Try with a below code snippet:
TextField(
onTap: () {
// To Do
},
// TextField Property
);
gesture detector doesn't work with textField try adding ignorePointer
GestureDetector(
child: new IgnorePointer (
child: new TextField(
.......
))),
onTap: () {
//do some thing
},
The TextField has its onTap property, I think this is the cause of your problem. You should you onTap property of TextField or use another suitable widget

How can I change the position of the Textformfield validation text ? Flutter

I made a custom form and when I want to use the validation. My issue is that the error message is not appearing as I wanted. Here's the screenshot of it.
So I want to change the position of the error message to be below my container. Does anyone have any idea how to do it ?
Here's the code of the form:
class AuthForm extends StatefulWidget {
final bool isPassword;
final IconData prefixIcon;
final String hintText;
late bool isPasswordVisible = isPassword;
final bool isCalendar;
final TextEditingController controller;
final bool isDropDown;
final bool isPhone;
final String? Function(String?)? validator;
AuthForm({Key? key, this.isPassword = false, required this.prefixIcon, required this.hintText,
this.isCalendar = false, required this.controller, this.isDropDown = false, this.isPhone = false, required this.validator}) : super(key: key);
#override
State<AuthForm> createState() => _AuthFormState();
}
class _AuthFormState extends State<AuthForm> {
#override
void initState() {
super.initState();
if (widget.isPhone){
getCountryCode();
}
}
start () async {
await CountryCodes.init();
}
Locale? getCountryCode () {
start();
final Locale? deviceLocale = CountryCodes.getDeviceLocale();
final CountryDetails details = CountryCodes.detailsForLocale();
return deviceLocale;
}
DateTime selectedDate = DateTime(2000,1);
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(1950, 1),
lastDate: DateTime.now());
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = picked;
});
}
}
#override
Widget build(BuildContext context) {
return Container(
width: 70.w,
height: 5.h,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.13.w,
color: Theme.of(context).splashColor,
),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(
widget.prefixIcon,
color: Theme.of(context).primaryColor,
size: 6.w,
),
widget.isDropDown ? const DropDownBar() :
Expanded(
child: TextFormField(
//autovalidateMode: AutovalidateMode.onUserInteraction,
validator: widget.validator,
keyboardType: widget.isPhone ? TextInputType.phone : TextInputType.text,
inputFormatters: [DialCodeFormatter()],
controller: widget.controller,
textAlign: TextAlign.center,
obscureText: widget.isPasswordVisible,
style: Theme.of(context).textTheme.bodyText2,
decoration: InputDecoration(
hintText : widget.hintText,
hintStyle: Theme.of(context).textTheme.bodyText1,
border: InputBorder.none,
),
onTap: () async {
if (widget.isCalendar){
//Dismiss the keyboard
FocusScope.of(context).requestFocus(FocusNode());
//Call the calendar
await _selectDate(context);
widget.controller.text = DateFormat('dd-MM-yyyy').format(selectedDate);
}
}
),
),
//Show Icon only if the form is for a Password
Visibility(
visible: widget.isPassword,
//Maintain the space where the widget is even if it is hid
maintainAnimation: true,
maintainState: true,
maintainSize: true,
child: InkWell(
highlightColor : Colors.transparent,
splashColor: Colors.transparent,
child: Icon(
widget.isPasswordVisible ? Icons.visibility : Icons.visibility_off,
color: Theme.of(context).primaryColor,
size: 6.w,
),
onTap: () {
setState(() {
widget.isPasswordVisible = !widget.isPasswordVisible;
});
},
),
),
],
),
);
}
}
Thanks for your suggestion,
Chris
You should try below piece of code for TextFormField
TextFormField(
textInputAction: TextInputAction.next,
style: TextStyle(fontSize: 16, height: 1),
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10)],
keyboardType: TextInputType.phone,
validator: (val) {
if (val!.isEmpty) {
return 'Please enter your mobile number';
} else if (val.length < 10) {
return 'Mobile number should be 10 digits only';
}
},
controller: bloc.mobileNumberLoginController,
decoration: InputDecoration(
filled: true,
border: OutlineInputBorder(
borderSide: BorderSide(
color: code != null ? HexColor(code!) : Colors.pink,
)),
contentPadding: EdgeInsets.only(top: 5, bottom: 5, left: 10, right: 10),
),
),

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.

How To Create Custom TextField Class?

I am trying to create a custom textfield class so that i can easily change how it looks by only editing in one place. Currently i am facing a problem on how to make a toggle password visibility.
This is what my custom textfield class:
class CustomTextFieldOutline extends StatelessWidget {
CustomTextFieldOutline(
{this.label,
this.controller,
this.isValid,
this.invalidMsg});
final String label;
final TextEditingController controller;
final bool isValid;
final String invalidMsg;
#override
Widget build(BuildContext context) {
return TextField(
decoration: InputDecoration(
labelText: label,
errorText: isValid ? null : invalidMsg,
errorStyle: TextStyle(color: constant.colorWhite),
labelStyle: TextStyle(color: constant.colorWhite),
errorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: constant.colorWhite),
),
focusedErrorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: constant.colorWhite),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: constant.colorWhite),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: constant.colorWhite),
)),
style: TextStyle(color: constant.colorWhite),
controller: controller,
);
}
}
This is how i call it:
final _resetPasswordView = Container(
child: AnimatedOpacity(
opacity: _resetPasswordVisibility ? 1.0 : 0.0,
duration: Duration(milliseconds: 300),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
CustomTextFieldOutline(
label: constant.email,
controller: _emailSignInController,
isValid: _isEmailValid,
invalidMsg: _emailValidMsg,
)
],
),
),
)
This how my password textfield looks like. I am not using custome textfield class because i dont know how can i implement toggle password visibility in class:
TextField(
decoration: InputDecoration(
labelText: constant.password,
suffixIcon: GestureDetector(
onTap: () {
_togglePasswordVisibility();
},
child: Icon(
_isHidePassword ? Icons.visibility_off : Icons.visibility,
color: constant.colorWhite,
),
),
errorText: _isPasswordValid ? null : _passwordValidMsg,
errorStyle: TextStyle(color: constant.colorWhite),
labelStyle: TextStyle(color: constant.colorWhite),
errorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: constant.colorWhite),
),
focusedErrorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: constant.colorWhite),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: constant.colorWhite),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: constant.colorWhite),
)),
style: TextStyle(color: constant.colorWhite),
obscureText: _isHidePassword,
controller: _passwordSignUpController,
)
How can i incorporate the password textfield functionality in the custom class so that i can use it for password or email textfield?
Here is a sample from instaflutter
class CustomTextField extends StatefulWidget {
final String hint;
final TextEditingController controller;
final Color baseColor;
final Color borderColor;
final Color errorColor;
final TextInputType inputType;
final bool obscureText;
final Function validator;
final Function onChanged;
CustomTextField(
{this.hint,
this.controller,
this.onChanged,
this.baseColor,
this.borderColor,
this.errorColor,
this.inputType = TextInputType.text,
this.obscureText = false,
this.validator});
_CustomTextFieldState createState() => _CustomTextFieldState();
}
class _CustomTextFieldState extends State<CustomTextField> {
Color currentColor;
#override
void initState() {
super.initState();
currentColor = widget.borderColor;
}
#override
Widget build(BuildContext context) {
return Card(
elevation: 0.0,
color: Colors.white,
shape: RoundedRectangleBorder(
side: BorderSide(color: currentColor, width: 2.0),
borderRadius: BorderRadius.circular(20.0),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: TextField(
obscureText: widget.obscureText,
onChanged: (text) {
if (widget.onChanged != null) {
widget.onChanged(text);
}
setState(() {
if (!widget.validator(text) || text.length == 0) {
currentColor = widget.errorColor;
} else {
currentColor = widget.baseColor;
}
});
},
//keyboardType: widget.inputType,
controller: widget.controller,
decoration: InputDecoration(
hintStyle: TextStyle(
color: widget.baseColor,
fontFamily: "OpenSans",
fontWeight: FontWeight.w300,
),
border: InputBorder.none,
hintText: widget.hint,
),
),
),
);
}
}
and then you can create your widgets
_emailField = new CustomTextField(
baseColor: Colors.grey,
borderColor: Colors.grey[400],
errorColor: Colors.red,
controller: _email,
hint: "E-mail Adress",
inputType: TextInputType.emailAddress,
validator: Validator.validateEmail,
);
_passwordField = CustomTextField(
baseColor: Colors.grey,
borderColor: Colors.grey[400],
errorColor: Colors.red,
controller: _password,
obscureText: true,
hint: "Password",
validator: Validator.validatePassword,
);
i do like this
Widget customTextField(TextInputType textInputType) {
RegExp pattern = RegExp(
r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+");
TextEditingController _textEditingController = TextEditingController();
if (textInputType == TextInputType.visiblePassword) {
return TextField(
controller: _textEditingController,
decoration: InputDecoration(hintText: 'Password'),
keyboardType: TextInputType.visiblePassword,
obscureText: true,
);
} else {
return TextField(
controller: _textEditingController,
decoration: InputDecoration(hintText: 'Email'),
keyboardType: TextInputType.emailAddress,
inputFormatters: [BlacklistingTextInputFormatter(pattern)],
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView(
shrinkWrap: true,
children: <Widget>[
customTextField(TextInputType.visiblePassword),
customTextField(TextInputType.emailAddress),
],
),
),
);
}
idk how to make RegExp for email, but the results will be like this:
class CustomTextFieldOutline extends StatefulWidget {
const CustomTextFieldOutline(
{Key key, this.hintText, this.label, this.onChange})
: super(key: key);
final String hintText;
final String label;
final void Function(String value) onChange;
#override
_CustomTextFieldOutlineState createState() => _CustomTextFieldOutlineState();
}
class _CustomTextFieldOutlineState extends State<CustomTextFieldOutline> {
bool showPassword = false;
TextEditingController controller = TextEditingController();
void _onChange(String value) {
controller.text = value;
if (widget.onChange != null) {
widget.onChange(value);
}
}
void _changePwdType() {
setState(() {
showPassword = !showPassword;
});
}
#override
Widget build(BuildContext context) {
return TextField(
obscureText: showPassword,
controller: controller,
onChanged: (value) => _onChange,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(showPassword ? Icons.visibility : Icons.visibility_off),
onPressed: _changePwdType,
),
hintText: widget.hintText != null ? widget.hintText : null,
labelText: widget.label != null ? widget.label : null,
),
);
}
}
You can try implement OutlineTextFormField Widget for Custom TextField of my company. I suppose it's pretty easy to extend, and it's include many utilities inside.
**
First, create outline_text_form_field.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
const int _defaultMinLinesOnMultiline = 6;
class OutlineTextFormField extends FormField<String> {
/// Creates a [FormField] that contains a [TextField].
///
/// When a [controller] is specified, [initialValue] must be null (the
/// default). If [controller] is null, then a [TextEditingController]
/// will be constructed automatically and its `text` will be initialized
/// to [initialValue] or the empty string.
///
/// For documentation about the various parameters, see the [TextField] class
/// and [new TextField], the constructor.
OutlineTextFormField({
Key? key,
required this.data,
bool updateStateIfInternalDataChanged = false,
this.controller,
String? initialValue,
FocusNode? focusNode,
InputDecoration? decoration,
String? hintText,
TextInputType? keyboardType,
TextCapitalization textCapitalization = TextCapitalization.none,
TextInputAction? textInputAction,
TextStyle? style,
StrutStyle? strutStyle,
TextDirection? textDirection,
TextAlign textAlign = TextAlign.start,
TextAlignVertical? textAlignVertical,
bool autofocus = false,
bool readOnly = false,
ToolbarOptions? toolbarOptions,
bool? showCursor,
String obscuringCharacter = '•',
bool obscureText = false,
bool autocorrect = true,
bool autoTrimWhenTyping = false,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
bool enableSuggestions = true,
MaxLengthEnforcement? maxLengthEnforcement,
int maxLines = 1,
int? minLines, // when multiline = true, minLines = 6
bool expands = false,
int? maxLength,
bool multiline = false,
ValueChanged<String>? onChanged,
GestureTapCallback? onTap,
VoidCallback? onEditingComplete,
ValueChanged<String>? onFieldSubmitted,
FormFieldSetter<String>? onSaved,
FormFieldValidator<String>? validator,
List<TextInputFormatter>? inputFormatters,
bool? enabled,
double cursorWidth = 2.0,
double? cursorHeight,
Radius? cursorRadius,
Color? cursorColor,
Brightness? keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
bool enableInteractiveSelection = true,
TextSelectionControls? selectionControls,
InputCounterWidgetBuilder? buildCounter,
ScrollPhysics? scrollPhysics,
Iterable<String>? autofillHints,
AutovalidateMode? autovalidateMode,
ScrollController? scrollController,
}) : assert(initialValue == null || controller == null),
assert(obscuringCharacter.length == 1),
assert(
maxLengthEnforcement == null,
'maxLengthEnforced is deprecated, use only maxLengthEnforcement',
),
assert(
(minLines == null) || (maxLines >= minLines),
"minLines can't be greater than maxLines",
),
assert(
!expands || minLines == null,
'minLines and maxLines must be null when expands is true.',
),
assert(!obscureText || maxLines == 1,
'Obscured fields cannot be multiline.'),
assert(maxLength == null || maxLength > 0),
super(
key: key,
initialValue: controller != null ? controller.text : initialValue,
onSaved: onSaved,
validator: validator,
enabled: enabled ?? decoration?.enabled ?? true,
autovalidateMode: autovalidateMode ??
(validator != null ? AutovalidateMode.onUserInteraction : null),
builder: (FormFieldState<String> field) {
if (updateStateIfInternalDataChanged &&
field.value != data) {
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
field.didChange(data);
});
}
final _OutlineTextFormFieldState state =
field as _OutlineTextFormFieldState;
final InputDecoration effectiveDecoration = InputDecoration(
hintStyle: TextStyle(
color: Colors.grey,
fontSize: 15,
fontWeight: FontWeight.w400,
),
hintMaxLines: 2,
errorMaxLines: 2,
helperMaxLines: 2,
contentPadding: EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
filled: true,
fillColor: Colors.white,
floatingLabelBehavior: FloatingLabelBehavior.never,
).copyWith(hintText: hintText);
// .applyDefaults(Theme.of(field.context).inputDecorationTheme);
void onChangedHandler(String value) {
field.didChange(autoTrimWhenTyping ? value.trim() : value);
if (onChanged != null) {
onChanged(value);
}
}
return TextField(
controller: state._effectiveController,
focusNode: focusNode,
decoration:
effectiveDecoration.copyWith(errorText: field.errorText),
keyboardType: keyboardType,
textInputAction: textInputAction,
style: style ?? TextStyle(
color: Colors.black,
fontSize: 15,
fontWeight: FontWeight.w400,
),
strutStyle: strutStyle,
textAlign: textAlign,
textAlignVertical: textAlignVertical,
textDirection: textDirection,
textCapitalization: textCapitalization,
autofocus: autofocus,
toolbarOptions: toolbarOptions,
readOnly: readOnly,
showCursor: showCursor,
obscuringCharacter: obscuringCharacter,
obscureText: obscureText,
autocorrect: autocorrect,
smartDashesType: smartDashesType ??
(obscureText
? SmartDashesType.disabled
: SmartDashesType.enabled),
smartQuotesType: smartQuotesType ??
(obscureText
? SmartQuotesType.disabled
: SmartQuotesType.enabled),
enableSuggestions: enableSuggestions,
maxLengthEnforcement: maxLengthEnforcement,
maxLines: multiline ? null : maxLines,
minLines: minLines == null && multiline
? _defaultMinLinesOnMultiline
: minLines,
expands: expands,
maxLength: maxLength,
onChanged: onChangedHandler,
onTap: onTap,
onEditingComplete: onEditingComplete,
onSubmitted: onFieldSubmitted,
inputFormatters: inputFormatters,
enabled: enabled ?? decoration?.enabled ?? true,
cursorWidth: cursorWidth,
cursorHeight: cursorHeight,
cursorRadius: cursorRadius,
cursorColor: cursorColor,
scrollPadding: scrollPadding,
scrollPhysics: scrollPhysics,
keyboardAppearance: keyboardAppearance,
enableInteractiveSelection: enableInteractiveSelection,
selectionControls: selectionControls,
buildCounter: buildCounter,
autofillHints: autofillHints,
scrollController: scrollController,
);
},
);
/// Controls the text being edited.
///
/// If null, this widget will create its own [TextEditingController] and
/// initialize its [TextEditingController.text] with [initialValue].
final TextEditingController? controller;
late String data;
#override
_OutlineTextFormFieldState createState() => _OutlineTextFormFieldState();
}
class _OutlineTextFormFieldState extends FormFieldState<String> {
TextEditingController? _controller;
TextEditingController get _effectiveController =>
widget.controller ?? _controller!;
late StreamSubscription<String> dataStreamSubscription;
#override
OutlineTextFormField get widget => super.widget as OutlineTextFormField;
#override
void initState() {
super.initState();
if (widget.controller == null) {
_controller =
TextEditingController(text: widget.initialValue ?? widget.data);
}
setValue(widget.initialValue ?? widget.data);
_effectiveController.addListener(_handleControllerChanged);
dataStreamSubscription = Stream.value(widget.data).listen(_onData);
}
#override
void didUpdateWidget(OutlineTextFormField oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != oldWidget.controller) {
oldWidget.controller?.removeListener(_handleControllerChanged);
widget.controller?.addListener(_handleControllerChanged);
if (oldWidget.controller != null && widget.controller == null)
_controller =
TextEditingController.fromValue(oldWidget.controller!.value);
if (widget.controller != null) {
setValue(widget.controller!.text);
if (oldWidget.controller == null) _controller = null;
}
}
}
#override
void dispose() {
dataStreamSubscription.cancel();
_effectiveController.removeListener(_handleControllerChanged);
super.dispose();
}
#override
void didChange(String? value) {
super.didChange(value);
if (_effectiveController.text != value) {
_effectiveController.text = value ?? '';
_effectiveController.selection =
TextSelection.collapsed(offset: value?.length ?? 0);
}
widget.data = _effectiveController.text;
}
#override
void reset() {
// setState will be called in the superclass, so even though state is being
// manipulated, no setState call is needed here.
_effectiveController.text = widget.initialValue ?? widget.data;
super.reset();
}
void _handleControllerChanged() {
// Suppress changes that originated from within this class.
//
// In the case where a controller has been passed in to this widget, we
// register this change listener. In these cases, we'll also receive change
// notifications for changes originating from within this class -- for
// example, the reset() method. In such cases, the FormField value will
// already have been set.
if (_effectiveController.text != value)
didChange(_effectiveController.text);
}
void _onData(String value) {
if (_effectiveController.text != value) _effectiveController.text = value;
}
}
** After you can use it:
String? validateStringBlank(
String? value,
String msg, [
bool autoToLowerCase = true,
]) =>
(value == null || value.trim().isEmpty)
? "Vui lòng nhập ${autoToLowerCase ? msg.toLowerCase() : msg}"
: null;
OutlineTextFormField(
data: "Apple",
hintText: "Enter company name",
validator: (value) =>
validateStringBlank(value,"Company Name"),
),