How to create TextField with Clear button using TextEditingController? - flutter

I tried to create a TextField with clear button, but when i enter some value there's no clear button show up as I wanted to. Seems like it cant detect the changes to the _firstNameController.text. How can I solve this issue?
class TextFieldWithClearBtn extends StatefulWidget {
#override
_TextFieldWithClearBtnState createState() => _TextFieldWithClearBtnState();
}
class _TextFieldWithClearBtnState extends State<TextFieldWithClearBtn> {
final TextEditingController _firstNameController = TextEditingController();
#override
void dispose {
super.dispose();
_firstNameController.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
child: TextFormField(
controller: _firstNameController,
decoration: InputDecoration(
labelText: "First name",
suffixIcon: _firstNameController.text.isNotEmpty
? GestureDetector(
onTap: () {
WidgetsBinding.instance.addPostFrameCallback((_) => _firstNameController.clear());
},
child: Icon(Icons.clear, color: Colors.black38),
)
: null
),
),
);
}
}

Instead of suffixIcon, you suffix. This way the clear button will not be visible if textformfield is not in focus and will display the icon when you tap on the field. Also, when you will tap on the clear icon after typing something, it'll clear the field. Working sample code below:
TextFormField(
controller: _firstNameController,
textAlign: TextAlign.left,
cursorColor: Colors.white,
onChanged: (value) {
},
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: 'First Name',
suffix: GestureDetector(
onTap: () {
_firstNameController.clear();
},
child: Icon(Icons.clear)
)
),
),
Hope this helps.

Related

How to select a value from a ListView and have it reflected in a TextFormField in flutter?

403 / 5.000
Hi, I'm new to Flutter and I want to replicate this functionality from an app.
This is the screen to add a new product, and it asks me to select a category.
enter image description here
Clicking opens a new screen with the available categories.
enter image description here
Then when you go back, the selected value is reflected in the form.
enter image description here
I have tried but I can't do it correctly. I share what I did.
from this textformField I call another screen
TextFormField(
controller: nombreCategoriaController,
keyboardType: TextInputType.name,
decoration: Formulario.cajaTexto(
hintText: capitalize1Word('Ingrese categoria'),
labelText: 'Categoria',
icono: Icons.air),
onChanged: ((valor) {}),
onTap: () {
Navigator.pushNamed(context, V_GLOBAL_RUTA_ADM_CATEGORIA);
},
),
Here I try to send a test value to the previous screen but without success.
return Scaffold(
appBar: AppBar(
toolbarHeight: 70.0,
centerTitle: true,
title: Text('CATEGORIA',
style: const TextStyle(
fontSize: 22,
color: Colors.white,
fontFamily: V_TIPO_FUENTE_MYRIAD,
)),
backgroundColor: V_GLOBAL_COLOR_PRIMARY_APP,
actions: const [
CerrarSesion(),
],
),
body: TextFormField(
keyboardType: TextInputType.name,
decoration: Formulario.cajaTexto(
hintText: capitalize1Word('Ingrese unidad de medida'),
labelText: 'Unidad de medida',
icono: Icons.air),
onChanged: ((valor) {}),
onTap: () {
V_GLOBAL_PRUEBA = 'prueba';
Navigator.pop(context);
},
),
);
Here is a working result in your case. You need to edit some decoration.
I wrote comments to explain what you needed to do!
Thanks!
class SettingsPage extends StatefulWidget {
const SettingsPage({Key? key}) : super(key: key);
#override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
final nombreCategoriaController = TextEditingController();
#override
void dispose() {
nombreCategoriaController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: TextFormField(
controller: nombreCategoriaController,
keyboardType: TextInputType.name,
decoration: const InputDecoration(
hintText: 'Ingrese categoria',
labelText: 'Categoria',
),
onChanged: ((valor) {}),
onTap: () async {
// If you wait here for the result that you pop from another Screen, you can assign that value
// to the textfield controller.
final result = await Navigator.push<String>(
context,
MaterialPageRoute<String>(
builder: (context) => CategoryScreen()));
nombreCategoriaController.text = result?.toString() ?? "";
},
),
);
}
}
class CategoryScreen extends StatelessWidget {
const CategoryScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 70.0,
centerTitle: true,
title: const Text('CATEGORIA',
style: TextStyle(
fontSize: 22,
color: Colors.white,
)),
backgroundColor: Colors.grey,
),
body: TextFormField(
keyboardType: TextInputType.name,
decoration: const InputDecoration(
hintText: 'Ingrese unidad de medida',
labelText: 'Unidad de medida',
),
onChanged: ((valor) {}),
onTap: () {
//TODO: return any value that you want to assign in the previous page.
String newVal = 'prueba';
Navigator.pop(context, newVal);
},
),
);
}
}

Why in Flutter, when I go to another page, the text in TextField disappears?

I enter text in TextField. When I click a button to go to another page and back, the text in the TextField disappears. Has anyone encountered this problem and can give me some advice?
My code:
Column(
children: [
ListTile(
onTap: () async {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => …)); // In fact, I used go_router, just replaced it with the Navigator that everyone should know
},
leading: const Icon(Icons.location_pin, color: Colors.black),
title: Text("Location"),
),
ListTile(
leading: const Icon(Icons.message, color: Colors.black),
title: TextFormField(
controller: messageController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Message",
),
),
),
],
),
Feel free to leave a comment if you need more information.
Why in Flutter, when I go to another page, the text in TextField disappears? I would appreciate any help. Thank you in advance!
This is because you create the TextEditingController when you navigate to this page so it will be created with empty text.
So you can make the controller global not inside StatefulWidget.
import 'package:flutter/material.dart';
class SearchScrean extends StatefulWidget {
const SearchScrean({super.key});
#override
State<SearchScrean> createState() => _SearchScreanState();
}
class _SearchScreanState extends State<SearchScrean> {
// you probably initialize the controller here or in initState.
TextEditingController messageController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
TextFormField(
controller: messageController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Message",
),
),
]),
);
}
}
See the comment.
You have to initialize it outside like this or use a state management package like bloc or provider :
import 'package:flutter/material.dart';
// initialize it here or in another global file
TextEditingController messageController = TextEditingController();
class SearchScrean extends StatefulWidget {
const SearchScrean({super.key});
#override
State<SearchScrean> createState() => _SearchScreanState();
}
class _SearchScreanState extends State<SearchScrean> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
TextFormField(
controller: messageController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Message",
),
),
]),
);
}
}
and when you don't want it anymore dispose it using messageController.dispose()

Flutter:How to automatically clear my TextFields?

I want to recreate automatically my page and clear all my TextFields on button press.Is there a method to do it?
Edited :
Here is an example of how to achieve this :
class _MyWidgetState extends State<MyWidget> {
final myAController = TextEditingController();
final myBController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
FlatButton(
onPressed: () {
setState(() {
myAController.clear();
myBController.clear();
});
},
child: Text(
"Clear text fields",
),
),
TextField(
controller: myAController,
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter value A'),
),
TextField(
controller: myBController,
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter Value B'),
),
],
),
),
);
}
}
As you can see every time i press my flatbutton the widget reloads with the updated counter value.
Here is an updated working demo.
I think it's
setState((){
// If there is anything you want to change put it here
});
This will rebuild the widget.

Flutter - Hide hint text when Text Field have focus

I need to know how to hide the hint text when I focus on the text field. This is my code:
class _ContactoState extends State<Contacto> {
FocusNode focusMsj;
#override
void initState() {
super.initState();
focusMsj = FocusNode();
focusMsj.addListener(() {
if (!focusMsj.hasFocus) {
FocusScope.of(context).requestFocus(focusMsj);
}
});
}
TextField(
focusNode: focusMsj,
hintText: focusMsj.hasFocus ? ' ' : 'Hint Text',)
return WillPopScope(
child: Listener(
onPointerUp: (e) {
focusMsj.hasFocus ? FocusScope.of(context).requestFocus(FocusNode()): '';
},
Thank you
For doing that matter you need to make something like that
class Play extends StatefulWidget {
#override
_PlayState createState() => _PlayState();
}
class _PlayState extends State<Play> {
FocusNode focusNode = FocusNode();
String hintText = 'Hello , iam Hint';
#override
void initState() {
// TODO: implement initState
super.initState();
focusNode.addListener(() {
if (focusNode.hasFocus) {
hintText = '';
} else {
hintText = 'Hello , iam Hint';
}
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () {
print(focusNode.hasFocus);
}),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
focusNode: focusNode,
decoration: InputDecoration(
hintText: hintText,
),
),
TextField(
decoration: InputDecoration(
hintText: '!!',
),
),
],
),
),
);
}
}
Shortly i listened to TextField by its focusNode property . When TextField has focus i make hintText property equal empty String value
There is a property for that:
TextField(decoration: InputDecoration(hasFloatingPlaceholder: false));
Edit: The version above is deprecated, the new version is:
TextField(decoration: InputDecoration(floatingLabelBehavior: FloatingLabelBehavior.never,),),
One simple solution you can try is define labelText with FloatingBehavior.never
TextField(
decoration: InputDecoration(
labelText: "Search",
floatingLabelBehavior: FloatingLabelBehavior.never,
)
)
HintText will be shown when it is not focussed. On focus, hint text will disappear.
Simply, don't add the hintText in the InputDecoration and mention only the labelText: 'Label' alongside labelStyle if you want to change the style of the label.
TextField(
decoration: InputDecoration(
labelText: "Label",
labelStyle: TextStyle(
color: Colors.blueGrey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
)
)
My understanding is there is no way to implement it without custom code. Hiding hintText when focused with "border: InputBorder.none," gives perfect login widget example as FloatingLabelBehavior and having animated labelText just won't do. floatingLabelBehavior: FloatingLabelBehavior.never - helps in some situtations but not the exact thing we wanted. If you have labelText and hintText FloatingLabelBehavior.never is helpful to control hiding and showing hintText and animating labelText above the field. WIth custom code we have
String emailHintText = "E-mail";
on the top of state class. Then:
Container(
decoration: BoxDecoration(
border: Border.all(color: white),
borderRadius: BorderRadius.circular(5)),
child: Padding(
padding: EdgeInsets.all(10),
child:
Focus(
onFocusChange: (hasFocus) {
if (hasFocus) {
setState(() {});
emailHintText = "";
}
else {
setState(() {});
emailHintText = "E-mail";
}
},
child: TextFormField(
decoration: InputDecoration(
hintText: emailHintText,
border: InputBorder.none))));
Now, you should know that using setState is a bit costly operation and may not be the best option if not used for very important functionalities.
This works perfectly for me. Just add on InputDecoration ( floatingLabelBehavior: FloatingLabelBehavior.never) of TextField or TextFormField.
TextFormField( controller:_controller, decoration : InputDecoration( label: Text('Entrer your code here '), floatingLabelBehavior: FloatingLabelBehavior.never, ), );

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: () {
},
)