How to show and hide password in TextFormField? [duplicate] - flutter

This question already has answers here:
How to show/hide password in TextFormField?
(14 answers)
Closed 3 months ago.
I want to add eye to password field in flutter project
this is my code:
TextFormField(
decoration: const InputDecoration(
label: Text('PASSWORD'),
),
keyboardType: TextInputType.visiblePassword,
obscureText: true,
validator: (val) {
if (val!.length < 6) {
return "Please enter at least 6 characters";
}
return null;
},
onSaved: (val) => data['password'] = val!,
),

You can use this custom widget:
class CustomInput extends StatefulWidget {
final String? label;
final TextInputType? keyboardType;
final String? Function(String?)? validator;
final Function(String?)? onSaved;
final bool obscureText;
const CustomInput(
{Key? key,
this.label,
this.keyboardType,
this.validator,
this.onSaved,
this.obscureText = false})
: super(key: key);
#override
State<CustomInput> createState() => _CustomInputState();
}
class _CustomInputState extends State<CustomInput> {
bool showPassword = false;
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(
label: Text(widget.label ?? ''),
suffixIcon: InkWell(
onTap: () {
setState(() {
showPassword = !showPassword;
});
},
child: Icon(showPassword
? Icons.remove_red_eye
: Icons.remove_red_eye_outlined),
)),
keyboardType: widget.keyboardType,
obscureText: showPassword ? false : widget.obscureText,
validator: widget.validator,
onSaved: widget.onSaved,
);
}
}
and use it like this:
CustomInput(
label: 'PASSWORD',
keyboardType: TextInputType.visiblePassword,
onSaved: (val) => data['password'] = val!,
validator: (val) {
if (val!.length < 6) {
return "Please enter at least 6 characters";
}
return null;
},
obscureText: true,
)

Add a suffixIcon in the decoration part :
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: showHideText(),
icon: Icon(Icons.yourIcon),
),
),

Create a boolean variable which will hold the status of password of being shown or not.
bool hidePassword=true;
Now, add this TextFormField with obscureText property.
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
obscureText: hidePassword,
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.password,
),
suffixIcon: IconButton(
onPressed: () {
setState(() {
hidePassword = !hidePassword;
});
},
icon: (hidePassword == true)
? const Icon(Icons.visibility_off)
: const Icon(
Icons.visibility,
),
),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(20),
),
),
hintText: 'Enter your password.',
),
validator: validatePassword,
),

Related

How to validate textfield when posting to firestore in flutter? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 11 months ago.
Improve this question
I have added validator in TextField but validator is not working while submitting data. Save button saves the data with null value.
import 'package:cloud_firestore/cloud_firestore.dart';
import '../../screens/orders/order_success.dart';
import '../../services/global_methods.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class PlaceOrder extends StatefulWidget {
static const routeName = '/place-order';
const PlaceOrder({Key? key}) : super(key: key);
#override
State<PlaceOrder> createState() => _PlaceOrderState();
}
class _PlaceOrderState extends State<PlaceOrder> {
final _formKey = GlobalKey<FormState>();
String? _name;
int? _phoneNumber;
String? _fullAddress;
String? _area;
String? _city;
String? _addressType;
final TextEditingController _addressController = TextEditingController();
String? _addressValue;
String? _deliverySlotValue;
final FirebaseAuth _auth = FirebaseAuth.instance;
late bool _isLoading = false;
final GlobalMethods _globalMethods = GlobalMethods();
final FocusNode _numberFocusNode = FocusNode();
#override
void initState() {
setState(() {
_isLoading = false;
});
super.initState();
}
#override
void dispose() {
_numberFocusNode.dispose();
super.dispose();
}
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
}
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance
.collection('orders')
.doc(orderId)
.set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add new address'),
),
body: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
DropdownButton<String>(
items: const [
DropdownMenuItem<String>(
child: Text('Home'),
value: 'Home',
),
DropdownMenuItem<String>(
child: Text('Office'),
value: 'Office',
),
DropdownMenuItem<String>(
child: Text('Other'),
value: 'Other',
),
],
onChanged: (value) {
setState(() {
_addressValue = value.toString();
_addressController.text = value.toString();
print(_addressType);
});
},
hint: const Text('Select address type'),
value: _addressValue,
),
const SizedBox(
width: 10,
),
Expanded(
child: TextFormField(
key: const ValueKey('addressType'),
controller: _addressController,
validator: (val) {
if (val!.isEmpty) {
return 'Please select address type';
}
return null;
},
decoration: const InputDecoration(
labelText: 'Select your address type',
),
onSaved: (val) {
_addressType = val.toString();
},
),
),
],
),
TextFormField(
onSaved: (value) {
_name = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('name'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your name';
}
return null;
},
decoration: InputDecoration(
labelText: 'Name',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_fullAddress = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('streetAddress'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your address';
}
return null;
},
decoration: InputDecoration(
labelText: 'Address',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_area = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('area'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your area';
}
return null;
},
decoration: InputDecoration(
labelText: 'Area',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_city = value!;
},
textInputAction: TextInputAction.next,
key: const ValueKey('city'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter your city';
}
return null;
},
decoration: InputDecoration(
labelText: 'City',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.location_city),
),
),
const SizedBox(height: 8),
TextFormField(
onSaved: (value) {
_phoneNumber = int.parse(value!);
},
textInputAction: TextInputAction.next,
// keyboardType: TextInputType.emailAddress,
onEditingComplete: () =>
FocusScope.of(context).requestFocus(_numberFocusNode),
key: const ValueKey('number'),
validator: (value) {
if (value!.length < 10) {
return 'Phone number must be 10 units';
}
return null;
},
decoration: InputDecoration(
labelText: 'Phone Number',
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
prefixIcon: const Icon(Icons.phone),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DropdownButton<String>(
items: const [
DropdownMenuItem<String>(
child: Text('11:00am to 1:00pm'),
value: '11:00am to 1:00pm',
),
DropdownMenuItem<String>(
child: Text('1:00pm to 3:00pm'),
value: '1:00pm to 3:00pm',
),
DropdownMenuItem<String>(
child: Text('3:00pm to 5:00pm'),
value: '3:00pm to 5:pm',
),
DropdownMenuItem<String>(
child: Text('5:00pm to 7:00pm'),
value: '5:00pm to 7:00pm',
),
DropdownMenuItem<String>(
child: Text('7:00pm to 9:pm'),
value: '7:00pm to 9:pm',
),
],
onChanged: (value) {
setState(() {
_deliverySlotValue = value.toString();
});
},
hint: const Text('Select Delivery Slot'),
value: _deliverySlotValue,
),
],
),
Padding(
padding: const EdgeInsets.all(48.0),
child: SizedBox(
child: ElevatedButton(
onPressed: _submitData,
child: _isLoading
? const CircularProgressIndicator()
: const Text(
'S U B M I T',
style: TextStyle(color: Colors.white),
),
),
),
),
],
),
),
),
),
);
}
}
It appears that the problem is a simple mistake with your control flow in the following code (see // comment):
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
} // <----- this should not be here!
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance
.collection('orders')
.doc(orderId)
.set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
}
As you can see, even if the user input is not valid, you still continue to upload the results to Firebase anyway.
Try correcting like this:
void _submitData() async {
final _isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (_isValid) {
_formKey.currentState!.save();
try {
setState(() {
_isLoading = true;
});
final User? user = _auth.currentUser;
final _uid = user!.uid;
final orderId = ModalRoute.of(context)!.settings.arguments as String;
FirebaseFirestore.instance.collection('orders').doc(orderId).set({
'userId': _uid,
'name': _name,
'phoneNumber': _phoneNumber,
'addressType': _addressType,
'address': _fullAddress,
'area': _area,
'city': _city,
'deliverySlot': _deliverySlotValue,
}, SetOptions(merge: true));
} catch (error) {
_globalMethods.authDialog(context, error.toString());
} finally {
setState(() {
_isLoading = false;
});
Navigator.of(context).pushReplacementNamed(OrderSuccess.routeName);
}
} else {
// do nothing
}
}
The only possible reason is because of _addressType it's initial value is null
since it's a String? variable so if the user didn't select a "Delivery Slot"
the result of it after saving would be null,
what you need to do, it to check the value of _addressType if it's null or not then proceed to saving the form because your form doesn't check it if it's null or not.
You can add the form validator to the submit button itself. The below code works when you click on the submit button.
Padding(
padding: const EdgeInsets.all(48.0),
child: SizedBox(
child: ElevatedButton(
onPressed: (){
if (_formKey.currentState!.validate()){
_submitData;
}
},
child: _isLoading
? const CircularProgressIndicator()
: const Text(
'S U B M I T',
style: TextStyle(color: Colors.white),
),
),
),
),

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

Flutter: Best way to get all values in a form

I'm making a data collection app which has multiple TextFields, like more than 12. I'm using a Form key to validate all of them. I want values of all the text fields so I can save them to firestore. How do I do this? Here's my code:
import 'package:flutter/material.dart';
class MainForm extends StatefulWidget {
#override
_MainFormState createState() => _MainFormState();
}
class _MainFormState extends State<MainForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Center(
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Text('Enter information about PG Owner'),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
autofocus: true,
textCapitalization: TextCapitalization.words,
textAlignVertical: TextAlignVertical.center,
onTap: () {},
decoration: InputDecoration(
prefixIcon: Icon(Icons.face),
labelText: 'Enter Name of Owner',
border: OutlineInputBorder()),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (value) {
if (value.length < 15) {
return 'Address seems very short!';
}
return null;
},
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(Icons.room),
labelText: 'Enter full address of Owner',
border: OutlineInputBorder()),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
keyboardType: TextInputType.number,
validator: (value) {
if (value.length < 9) {
return 'Phone number must be 9 digits or longer';
}
return null;
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.phone),
labelText: 'Phone number of Owner',
border: OutlineInputBorder()),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'Please enter a valid email address';
}
if (!value.contains('#')) {
return 'Email is invalid, must contain #';
}
if (!value.contains('.')) {
return 'Email is invalid, must contain .';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mail_outline),
labelText: 'Enter Email',
border: OutlineInputBorder()),
),
),
)
],
),
),
),
);
}
}
Update: I know that proper way (I've read the docs) of getting values from a TextField is by creating a controller. But, In my case there are 14 TextFields which requires me to create 14 controllers. Is there a better way of doing this?
You can use something like this in the following code:
_formKey.currentState.save(); calls the onSaved() on each textFormField items, which assigns the value to all the fields and you can use them as required. Try using the _formKey.currentState.save(); just after _formKey.currentState.validate() is evaluated as true.
The form code looks like this:
String contactNumber;
String pin;
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
onSaved: (String value){contactNumber=value;},
keyboardType: TextInputType.phone,
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
maxLength: 10,
decoration: InputDecoration(
labelText: "Enter Your Mobile Number",
hintText: "Number",
icon: Icon(Icons.phone_iphone)),
validator: (value) {
if (value.isEmpty || value.length < 10) {
return 'Please Enter 10 digit number';
}
return null;
},
),
TextFormField(
onSaved: (String value){pin=value;},
keyboardType: TextInputType.phone,
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
maxLength: 10,
decoration: InputDecoration(
labelText: "Enter Your PIN",
hintText: "Number",
icon: Icon(Icons.lock)),
validator: (value) {
if (value.isEmpty || value.length < 6) {
return 'Please Enter 6 digit PIN';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
color: Colors.black,
textColor: Colors.white,
onPressed: () {
if (_formKey.currentState.validate()) {
***_formKey.currentState.save();***
bloc.loginUser(contactNumber, pin);
}
},
child: Text('Login' /*style: TextStyle(fontSize: 30),*/)),
)
],
),
);
Using controller in TextFormField, you can get value of the TextFormField.
TextEditingController emailEditingController = TextEditingController();
TextFormField(
controller: emailEditingController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter a valid email address';
}
if (!value.contains('#')) {
return 'Email is invalid, must contain #';
}
if (!value.contains('.')) {
return 'Email is invalid, must contain .';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mail_outline),
labelText: 'Enter Email',
border: OutlineInputBorder()),
);
Get Value like:
String email=emailEditingController.text;
Updated Answer
Get value by using onSubmitted
onSubmitted: (String value){email=value;},
I am not satisfied with how Flutter make you handle the form values yourself, you need to create a TextEditingController instance for each field, assign it to the controller and remember to dispose all of them manually. This leads to a lot of boilerplate code and makes it more error-prone:
final _formKey = GlobalKey<FormState>();
final controller1 = TextEditingController();
final controller2 = TextEditingController();
final controller3 = TextEditingController();
#override
void dispose() {
super.dispose();
controller1.dispose();
controller2.dispose();
controller3.dispose();
}
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(children: [
TextFormField(controller: controller1),
TextFormField(controller: controller2),
TextFormField(
controller: controller3,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
final value1 = controller1.text;
final value2 = controller2.text;
final value3 = controller3.text;
// do something with the form data
}
},
child: const Text('Submit'),
),
]),
);
}
A much less cumbersome way is to use the flutter_form_builder package and replace TextFormField with the FormBuilderTextField widget which is a wrapper of the old plain TextField. You can see all of the supported input widgets here.
All you need to do now is to specify the name of each field in your form, and access it in _formKey.currentState?.value. See the example below:
final _formKey = GlobalKey<FormBuilderState>();
#override
Widget build(BuildContext context) {
return FormBuilder(
key: _formKey,
child: Column(children: [
FormBuilderTextField(name: 'field1'),
FormBuilderTextField(name: 'field2'),
FormBuilderTextField(
name: 'field3',
validator: FormBuilderValidators.required(
context,
errorText: 'Please enter some text',
),
),
ElevatedButton(
onPressed: () {
_formKey.currentState.save();
if (_formKey.currentState!.validate()) {
final formData = _formKey.currentState?.value;
// formData = { 'field1': ..., 'field2': ..., 'field3': ... }
// do something with the form data
}
},
child: const Text('Submit'),
),
]),
);
}
You can use flutter_form_bloc, you don't need to create any TextEditingController and can separate the Business Logic from the User Interface, in addition to offering other advantages.
dependencies:
flutter_bloc: ^0.21.0
form_bloc: ^0.4.1
flutter_form_bloc: ^0.3.0
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:form_bloc/form_bloc.dart';
void main() => runApp(MaterialApp(home: MainForm()));
class MainFormBloc extends FormBloc<String, String> {
final nameField = TextFieldBloc();
final addressField = TextFieldBloc(validators: [
(value) => value.length < 15 ? 'Address seems very short!' : null,
]);
final phoneNumberField = TextFieldBloc(validators: [
(value) =>
value.length < 9 ? 'Phone number must be 9 digits or longer' : null,
]);
final emailField = TextFieldBloc(validators: [Validators.email]);
#override
List<FieldBloc> get fieldBlocs => [
nameField,
addressField,
phoneNumberField,
emailField,
];
#override
Stream<FormBlocState<String, String>> onSubmitting() async* {
// This method is called when you call [mainFormBloc.submit]
// and each field bloc have a valid value.
// And you can save them in firestore.
print(nameField.value);
print(addressField.value);
print(phoneNumberField.value);
print(emailField.value);
yield currentState.toSuccess('Data saved successfully.');
// yield `currentState.toLoaded()` because
// you can't submit if the state is `FormBlocSuccess`.
// In most cases you don't need to do this,
// because you only want to submit only once.
yield currentState.toLoaded();
}
}
class MainForm extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<MainFormBloc>(
builder: (context) => MainFormBloc(),
child: Builder(
builder: (context) {
final formBloc = BlocProvider.of<MainFormBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Main Form')),
body: FormBlocListener<MainFormBloc, String, String>(
onSuccess: (context, state) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.successResponse),
backgroundColor: Colors.green,
),
);
},
onSubmissionFailed: (context, state) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Some fields have invalid data.'),
backgroundColor: Colors.red,
),
);
},
child: ListView(
children: <Widget>[
TextFieldBlocBuilder(
textFieldBloc: formBloc.nameField,
padding: const EdgeInsets.all(8.0),
autofocus: true,
textCapitalization: TextCapitalization.words,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
prefixIcon: Icon(Icons.face),
labelText: 'Enter Name of Owner',
border: OutlineInputBorder()),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.addressField,
padding: const EdgeInsets.all(8.0),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(Icons.room),
labelText: 'Enter full address of Owner',
border: OutlineInputBorder()),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.phoneNumberField,
padding: const EdgeInsets.all(8.0),
keyboardType: TextInputType.number,
decoration: InputDecoration(
prefixIcon: Icon(Icons.phone),
labelText: 'Phone number of Owner',
border: OutlineInputBorder()),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.emailField,
padding: const EdgeInsets.all(8.0),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mail_outline),
labelText: 'Enter Email',
border: OutlineInputBorder()),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: formBloc.submit,
child: Center(child: Text('SUBMIT')),
),
),
],
),
),
);
},
),
);
}
}
I came here from a similar search. All the answers found did not satisfy my need, hence I wrote a custom solution.
form key
final _signUpKey = GlobalKey<FormState>();
declare your TextEditingController
final Map<String, TextEditingController> sigUpController = {
'firstName': TextEditingController(),
'lastName': TextEditingController(),
'email': TextEditingController(),
'phone': TextEditingController(),
'password': TextEditingController(),
};
Pass controller to TextFormField like this
Form(
key: _signUpKey,
child: Column(
children: [
TextFormField(
controller: sigUpController['firstName'],
validator: validator,
autofocus: autofocus,
keyboardType: TextInputType.text,
style: const TextStyle(
fontSize: 14,
),
onTap: onTap,
onChanged: onChanged,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r"[a-zA-Z]+|\s"),
),
],
),
// define the other TextFormField here
TextButton(
onPressed: () {
if (!_signUpKey.currentState!.validate()) {
return;
}
// To get data I wrote an extension method bellow
final data = sigUpController.data();
print('data: $data'); // data: {firstName: John, lastName: Doe, email: example#email.com, phone: 0000000000, password: password}
},
child: const Text('submit'),
)
],
),
);
Extension method to get data from Map<String, TextEditingController>
extension Data on Map<String, TextEditingController> {
Map<String, dynamic> data() {
final res = <String, dynamic>{};
for (MapEntry e in entries) {
res.putIfAbsent(e.key, () => e.value?.text);
}
return res;
}
}
Try using this flutter package flutter_form_builder, it will help you from repeating yourself by creating multiple controllers for each form field. In addition to that, it will help you in validating the form, and updating the form with simplicity by using only a simple form key to control the entire form.

Keep Getting this Error: 'package:flutter/src/widgets/text.dart': Failed assertion: line 241 pos 10: 'data != null'

Sorry for the large amount of code! I just started with flutter and am very new to programming as a whole. I am trying to make a functioning submittable form and followed a tutorial to do so, but I keep getting this error when I try to load the form page:
'package:flutter/src/widgets/text.dart': Failed assertion: line 241 pos 10: 'data != null'
I have attached the code, but if this is the wrong bit of code for the error let me know and I can attach the other lib files. When it works, I want this to be submittable form to a URL I have and JSON encoded.
I greatly appreciate any help!
I have tried removing all validation, and I have tried looking through the "null(s)", but am unsure which one one is throwing the error.
class MyFormPage extends StatefulWidget {
MyFormPage({Key key, this.title}) : super(key: key);
final String title;
#override
_FormPage createState() => new _FormPage();
}
class _FormPage extends State<MyFormPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new
GlobalKey<ScaffoldState>();
Contact newContact = new Contact();
final GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
List<String> _information = <String>[
'',
'male',
'female',
];
String _info = '';
final TextEditingController _controller = new TextEditingController();
Future _chooseDate(BuildContext context, String initialDateString) async {
var now = new DateTime.now();
var initialDate = convertToDate(initialDateString) ?? now;
initialDate = (initialDate.year >= 1900 && initialDate.isBefore(now)
? initialDate
: now);
var result = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: new DateTime(1900),
lastDate: new DateTime.now());
if (result == null) return;
setState(() {
_controller.text = new DateFormat.yMd().format(result);
});
}
DateTime convertToDate(String input) {
try {
var d = new DateFormat.yMd().parseStrict(input);
return d;
} catch (e) {
return null;
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text(widget.title),
),
body: new SafeArea(
top: false,
bottom: false,
child: new Form(
key: _formKey,
autovalidate: true,
child: new ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
children: <Widget>[
new TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.person),
hintText: 'Enter your first name',
labelText: 'First Name',
),
inputFormatters: [new LengthLimitingTextInputFormatter(15)],
validator: (val) =>
val.isEmpty ? 'First name is required' : null,
onSaved: (val) => newContact.firstName = val,
),
new TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.person),
hintText: 'Enter your last name',
labelText: 'Last Name',
),
inputFormatters: [new LengthLimitingTextInputFormatter(15)],
validator: (val) =>
val.isEmpty ? 'Last name is required' : null,
onSaved: (val) => newContact.lastName = val,
),
new Row(children: <Widget>[
new Expanded(
child: new TextFormField(
decoration: new InputDecoration(
icon: const Icon(Icons.calendar_today),
hintText: 'Enter your date of birth',
labelText: 'D.O.B.',
),
controller: _controller,
keyboardType: TextInputType.datetime,
onSaved: (val) => newContact.dob = convertToDate(val),
)),
new IconButton(
icon: new Icon(Icons.more_horiz),
tooltip: 'Choose date',
onPressed: (() {
_chooseDate(context, _controller.text);
}),
)
]),
new TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.phone),
hintText: 'Enter a phone number',
labelText: 'Phone',
),
keyboardType: TextInputType.phone,
inputFormatters: [
new WhitelistingTextInputFormatter(
new RegExp(r'^[()\d -]{1,15}$')),
],
validator: (value) => isValidPhoneNumber(value)
? null
: 'Phone number must be entered as (###)###-####',
onSaved: (val) => newContact.phone = val,
),
new TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.email),
hintText: 'Enter a email address',
labelText: 'Email',
),
keyboardType: TextInputType.emailAddress,
validator: (value) => isValidEmail(value)
? null
: 'Please enter a valid email address',
onSaved: (val) => newContact.email = val,
),
new FormField(
builder: (FormFieldState<String> state) {
return InputDecorator(
decoration: InputDecoration(
icon: const Icon(Icons.group),
labelText: 'Gender',
errorText: state.hasError ? state.errorText : null,
),
isEmpty: _info == '',
child: new DropdownButtonHideUnderline(
child: new DropdownButton<String>(
value: _info,
isDense: true,
onChanged: (String newValue) {
setState(() {
newContact.gender = newValue;
_info = newValue;
state.didChange(newValue);
});
},
items: _information.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
),
),
);
},
validator: (val) {
return val != '' ? null : 'Please select a gender';
},
),
new Container(
padding: const EdgeInsets.only(left: 40.0, top: 20.0),
child: new RaisedButton(
child: const Text('Submit'),
onPressed: _submitForm,
)),
],
))),
);
}
bool isValidPhoneNumber(String input) {
final RegExp regex = new RegExp(r'^\(\d\d\d\)\d\d\d\-\d\d\d\d$');
return regex.hasMatch(input);
}
bool isValidEmail(String input) {
final RegExp regex = new RegExp(
r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$");
return regex.hasMatch(input);
}
bool isValidDob(String dob) {
if (dob.isEmpty) return true;
var d = convertToDate(dob);
return d != null && d.isBefore(new DateTime.now());
}
void showMessage(String message, [MaterialColor color = Colors.red]) {
_scaffoldKey.currentState.showSnackBar(
new SnackBar(backgroundColor: color, content: new Text(message)));
}
void _submitForm() {
final FormState form = _formKey.currentState;
if (!form.validate()) {
showMessage('Form is not valid! Please review and correct.');
} else {
form.save(); //This invokes each onSaved event
print('Form save called, newContact is now up to date...');
print('First Name: ${newContact.firstName}');
print('Last Name: ${newContact.lastName}');
print('Dob: ${newContact.dob}');
print('Phone: ${newContact.phone}');
print('Email: ${newContact.email}');
print('Gender: ${newContact.gender}');
print('========================================');
print('Submitting to back end...');
var contactService = new ContactService();
contactService.createContact(newContact).then((value) => showMessage(
'New contact created for ${value.firstName}!', Colors.blue));
}
}
}
So, when I click the button to navigate to my form page I get the red screen showing the error code I have mentioned above. If it were to work correctly, a sign-up page should appear.
Your title maybe null, which when it goes to the Text widget would cause this error. You can add a default title as follows:
MyFormPage({Key key, this.title = ''}) : super(key: key);

How to add clear button to TextField Widget

Is there a proper way to add a clear button to the TextField?
Just like this picture from Material design guidelines:
What I found is to set a clear IconButton in the InputDecoration's suffixIcon. Is this the right way?
Output:
Create a variable
var _controller = TextEditingController();
And your TextField:
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Enter a message',
suffixIcon: IconButton(
onPressed: _controller.clear,
icon: Icon(Icons.clear),
),
),
)
Container(
margin: EdgeInsets.only(left: 16.0),
child: TextFormField(
controller: _username,
decoration: InputDecoration(
hintText: '请输入工号',
filled: true,
prefixIcon: Icon(
Icons.account_box,
size: 28.0,
),
suffixIcon: IconButton(
icon: Icon(Icons.remove),
onPressed: () {
debugPrint('222');
})),
),
),
use iconButton
Try this -
final TextEditingController _controller = new TextEditingController();
new Stack(
alignment: const Alignment(1.0, 1.0),
children: <Widget>[
new TextField(controller: _controller,),
new FlatButton(
onPressed: () {
_controller.clear();
},
child: new Icon(Icons.clear))
]
)
Here’s another answer expanding a bit on #Vilokan Lab’s answer, which wasn’t really doing it for me since FlatButton has a minimum width of 88.0, and thus the clear button was not appearing right-aligned with the TextField at all.
So I went ahead and made my own button class, and applied that using a Stack, here is my process:
Button class:
class CircleIconButton extends StatelessWidget {
final double size;
final Function onPressed;
final IconData icon;
CircleIconButton({this.size = 30.0, this.icon = Icons.clear, this.onPressed});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: this.onPressed,
child: SizedBox(
width: size,
height: size,
child: Stack(
alignment: Alignment(0.0, 0.0), // all centered
children: <Widget>[
Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle, color: Colors.grey[300]),
),
Icon(
icon,
size: size * 0.6, // 60% width for icon
)
],
)));
}
}
Then apply like so as InputDecoration to your TextField:
var myTextField = TextField(
controller: _textController,
decoration: InputDecoration(
hintText: "Caption",
suffixIcon: CircleIconButton(
onPressed: () {
this.setState(() {
_textController.clear();
});
},
)),
},
);
To get this:
Unhighlighted state
Highlighted / selected state.
Note this colouring comes free when you use suffixIcon.
Note you can also Stack it in your TextField like this, but you won't get the auto-colouring you get when you use suffixIcon:
var myTextFieldView = Stack(
alignment: Alignment(1.0,0.0), // right & center
children: <Widget>[
TextField(
controller: _textController,
decoration: InputDecoration(hintText: "Caption"),
),
Positioned(
child: CircleIconButton(
onPressed: () {
this.setState(() {
_textController.clear();
});
},
),
),
],
);
Search TextField with icon and clear button
import 'package:flutter/material.dart';
class SearchTextField extends StatefulWidget{
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return new SearchTextFieldState();
}
}
class SearchTextFieldState extends State<SearchTextField>{
final TextEditingController _textController = new TextEditingController();
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Row(children: <Widget>[
new Icon(Icons.search, color: _textController.text.length>0?Colors.lightBlueAccent:Colors.grey,),
new SizedBox(width: 10.0,),
new Expanded(child: new Stack(
alignment: const Alignment(1.0, 1.0),
children: <Widget>[
new TextField(
decoration: InputDecoration(hintText: 'Search'),
onChanged: (text){
setState(() {
print(text);
});
},
controller: _textController,),
_textController.text.length>0?new IconButton(icon: new Icon(Icons.clear), onPressed: () {
setState(() {
_textController.clear();
});
}):new Container(height: 0.0,)
]
),),
],);
}
}
TextFormField(
controller:_controller
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: (){
_controller.clear();
},
icon: Icon(
Icons.keyboard,
color: Colors.blue,
),
),
),
)
TextEditingController is used to check the current state of Text, where we can decide whether we can show the cancel icon or not, depend on the availability of Text.
var _usernameController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: TextField(
controller: _usernameController,
onChanged: (text) {
setState(() {});
},
decoration: InputDecoration(
labelText: 'Username',
suffixIcon: _usernameController.text.length > 0
? IconButton(
onPressed: () {
_usernameController.clear();
setState(() {});
},
icon: Icon(Icons.cancel, color: Colors.grey))
: null),
),
),
),
);
}
Output:
Here's a snippet of my code that works.
What it does: only show clear button if text field value is not empty
class _MyTextFieldState extends State<MyTextField> {
TextEditingController _textController;
bool _wasEmpty;
#override
void initState() {
super.initState();
_textController = TextEditingController(text: widget.initialValue);
_wasEmpty = _textController.text.isEmpty;
_textController.addListener(() {
if (_wasEmpty != _textController.text.isEmpty) {
setState(() => {_wasEmpty = _textController.text.isEmpty});
}
});
}
#override
void dispose() {
_textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return TextFormField(
controller: _textController,
decoration: InputDecoration(
labelText: widget.label,
suffixIcon: _textController.text.isNotEmpty
? Padding(
padding: const EdgeInsetsDirectional.only(start: 12.0),
child: IconButton(
iconSize: 16.0,
icon: Icon(Icons.cancel, color: Colors.grey,),
onPressed: () {
setState(() {
_textController.clear();
});
},
),
)
: null,
),);
...
TextField(
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(
Icons.cancel,
),
onPressed: () {
_controllerx.text = '';
}
),
)
)
To add icon inside the textfield. You must have to use suffixIcon or prefixIcon inside the Input decoration.
TextFormField(
autofocus: false,
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: Icon(
Icons.clear,
size: 20.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(0.0)),
),
hintText: 'Enter Password',
contentPadding: EdgeInsets.all(10.0),
),
);
Didn't want to go the StatefulWidget route. Here's an example using TextEditingController and StatelessWidget (with Providers pushing updates).
I keep the controller in the static field.
class _SearchBar extends StatelessWidget {
static var _controller = TextEditingController();
#override
Widget build(BuildContext context) {
var dictionary = Provider.of<Dictionary>(context);
return TextField(
controller: _controller,
autofocus: true,
onChanged: (text) {
dictionary.lookupWord = text;
},
style: TextStyle(fontSize: 20.0),
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Search',
suffix: GestureDetector(
onTap: () {
dictionary.lookupWord = '';
_controller.clear();
},
child: Text('x'),
)));
}
}
If you want a ready-to-use Widget, which you can just put in a file and then have a reusable element you can use everywhere by inserting ClearableTextField(), use this piece of code:
import 'package:flutter/material.dart';
class ClearableTexfield extends StatefulWidget {
ClearableTexfield({
Key key,
this.controller,
this.hintText = 'Enter text'
}) : super(key: key);
final TextEditingController controller;
final String hintText;
#override
State<StatefulWidget> createState() {
return _ClearableTextfieldState();
}
}
class _ClearableTextfieldState extends State<ClearableTexfield> {
bool _showClearButton = false;
#override
void initState() {
super.initState();
widget.controller.addListener(() {
setState(() {
_showClearButton = widget.controller.text.length > 0;
});
});
}
#override
Widget build(BuildContext context) {
return TextField(
controller: widget.controller,
decoration: InputDecoration(
hintText: widget.hintText,
suffixIcon: _getClearButton(),
),
);
}
Widget _getClearButton() {
if (!_showClearButton) {
return null;
}
return IconButton(
onPressed: () => widget.controller.clear(),
icon: Icon(Icons.clear),
);
}
}
Further explanations can be found on this page:
https://www.flutterclutter.dev/flutter/tutorials/text-field-with-clear-button/2020/104/
It also builds upon IconButton, but has the advantage of only displaying the clear button when there is text inside the textfield.
Looks like this:
You can also use TextFormField.
First create Form Key. final _formKeylogin = GlobalKey<FormState>();
Form(key: _formKeylogin,
child: Column(
children: [
Container(
margin: EdgeInsets.all(20.0),
child: TextFormField(
style: TextStyle(color: WHITE),
textInputAction: TextInputAction.next,
onChanged: (companyName) {
},
validator: (companyName) {
if (companyName.isEmpty) {
return 'Please enter company Name';
} else {
return null;
}
},
),
And in OnTap or onPress Method.
_formKeylogin.currentState.reset();
To clear a button when the input it is not empty and with focus.
Create a controller variable:
//Clear inputs
final _nameInputcontroller = TextEditingController();
Create a FocusNode:
late FocusNode focusNodeNameInput;
#override
void initState() {
super.initState();
//FocusNode for all inputs
focusNodeNameInput = FocusNode();
focusNodeNameInput.addListener(() {
setState(() {});
});
}
#override
void dispose() {
// Clean up the focus node when the Form is disposed.
focusNodeNameInput.dispose();
super.dispose();
}
And the TextFormField:
TextFormField(
controller: _nameInputcontroller,
focusNode: focusNodeNameInput,
decoration: InputDecoration(
labelText: LocaleKeys.name.tr(),
labelStyle: TextStyle(
color: focusNodeNameInput.hasFocus ? AppColors.MAIN_COLOR : null,
),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: AppColors.MAIN_COLOR,
),
),
suffixIcon: _nameInputcontroller.text.isNotEmpty || focusNodeNameInput.hasFocus
? IconButton(
onPressed: _nameInputcontroller.clear,
icon: const Icon(
Icons.clear,
color: AppColors.MAIN_COLOR,
),
) : null,
),
),
Robust solution based on the code written by the Flutter team
Here is a fully reusuable ClearableTextFormField with maximum configuration, most of the code for this clearable text field here is from the commits on Apr 1, 2021 of the Flutter team for the built-in TextFormField. The ClearableTextFormField accepts the same params as and works similarly to the built-in TextFormField.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// A [TextFormField] with a clear button
class ClearableTextFormField 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.
ClearableTextFormField({
Key? key,
this.controller,
String? initialValue,
FocusNode? focusNode,
InputDecoration decoration = const InputDecoration(),
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,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
bool enableSuggestions = true,
MaxLengthEnforcement? maxLengthEnforcement,
int? maxLines = 1,
int? minLines,
bool expands = false,
int? maxLength,
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,
// Features
this.resetIcon = const Icon(Icons.close),
}) : assert(initialValue == null || controller == null),
assert(obscuringCharacter.length == 1),
assert(
maxLengthEnforcement == null,
'maxLengthEnforced is deprecated, use only maxLengthEnforcement',
),
assert(maxLines == null || maxLines > 0),
assert(minLines == null || minLines > 0),
assert(
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
"minLines can't be greater than maxLines",
),
assert(
!expands || (maxLines == null && 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 ?? true,
autovalidateMode: autovalidateMode ?? AutovalidateMode.disabled,
builder: (FormFieldState<String> field) {
final _ClearableTextFormFieldState state =
field as _ClearableTextFormFieldState;
final InputDecoration effectiveDecoration = decoration
.applyDefaults(Theme.of(field.context).inputDecorationTheme);
void onChangedHandler(String value) {
field.didChange(value);
if (onChanged != null) onChanged(value);
}
return Focus(
onFocusChange: (hasFocus) => state.setHasFocus(hasFocus),
child: TextField(
controller: state._effectiveController,
focusNode: focusNode,
decoration: effectiveDecoration.copyWith(
errorText: field.errorText,
suffixIcon:
((field.value?.length ?? -1) > 0 && state.hasFocus)
? IconButton(
icon: resetIcon,
onPressed: () => state.clear(),
color: Theme.of(state.context).hintColor,
)
: null,
),
keyboardType: keyboardType,
textInputAction: textInputAction,
style: style,
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: maxLines,
minLines: minLines,
expands: expands,
maxLength: maxLength,
onChanged: onChangedHandler,
onTap: onTap,
onEditingComplete: onEditingComplete,
onSubmitted: onFieldSubmitted,
inputFormatters: inputFormatters,
enabled: 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;
final Icon resetIcon;
#override
_ClearableTextFormFieldState createState() => _ClearableTextFormFieldState();
}
class _ClearableTextFormFieldState extends FormFieldState<String> {
TextEditingController? _controller;
bool hasFocus = false;
TextEditingController get _effectiveController =>
widget.controller ?? _controller!;
#override
ClearableTextFormField get widget => super.widget as ClearableTextFormField;
#override
void initState() {
super.initState();
if (widget.controller == null)
_controller = TextEditingController(text: widget.initialValue);
else
widget.controller!.addListener(_handleControllerChanged);
}
#override
void didUpdateWidget(ClearableTextFormField 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() {
widget.controller?.removeListener(_handleControllerChanged);
super.dispose();
}
#override
void didChange(String? value) {
super.didChange(value);
if (_effectiveController.text != value)
_effectiveController.text = value ?? '';
}
#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 ?? '';
super.reset();
}
void setHasFocus(bool b) => setState(() => hasFocus = b);
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);
}
/// Invoked by the clear suffix icon to clear everything in the [FormField]
void clear() {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_effectiveController.clear();
didChange(null);
});
}
}
Read more about the assert statement in the Dart language doc.
Below are the explanations for the additional code added to the built-in TextFormField
Optional resetIcon param for the ClearableTextFormField
🚀 Additional code inside _ClearableTextFormFieldState:
/// Invoked by the clear suffix icon to clear everything in the [FormField]
void clear() {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_effectiveController.clear();
didChange(null);
});
}
addPostFrameCallback() ensures the passed-in function is only called on Widget build complete (when the TextFormField has been fully built and rendered on the screen). U can read more about it in this thread.
The following state and setState allows u to keep track of the focus change in TextField (when the field is focused or unfocused):
bool hasFocus = false;
void setHasFocus(bool b) => setState(() => hasFocus = b);
🚀 Additional code inside the builder method of the extended FormField<String> (the super() parts):
Hide the clear button when the field's value is empty or null:
suffixIcon:
((field.value?.length ?? -1) > 0 && state.hasFocus)
? IconButton(
icon: resetIcon,
onPressed: () => state.clear(),
color: Theme.of(state.context).hintColor,
)
: null,
Rebuild the TextField on focus and unfocus to show and hide the clear button:
Focus(
onFocusChange: (hasFocus) => state.setHasFocus(hasFocus),
child: TextField(// more code here...),
)
IconButton(
icon: Icon(Icons.clear_all),
tooltip: 'Close',
onPressed: () {
},
)