TextField not accept text on first click Flutter - flutter

On my first page I have ListView.builder.
ListView.builder(
itemCount:_docLocatedLot.length,
itemBuilder: (context, index) => EditListTile(
model: _docLocatedLot[index], onChanged: (ItemsLocatedLot updatedModel) {
_docLocatedLot[index] = updatedModel;
},
),
)
Bellow is my source code of EditListTile, after click on edit button and click on TextField new value from keyboard not accept. After click again on TextField (second click) I can enter value in TextField.
How fix this problem?
Where is error in my code? Why I need two click to enter new value?
class EditListTile extends StatefulWidget {
final ItemsLocatedLot model;
final Function(ItemsLocatedLot listModel) onChanged;
const EditListTile({super.key, required this.model, required this.onChanged});
#override
State<EditListTile> createState() => _EditListTileState();
}
class _EditListTileState extends State<EditListTile> {
late ItemsLocatedLot model;
late bool _isEditingMode;
late TextEditingController _subTitleEditingController;
#override
void initState() {
super.initState();
model = widget.model;
_isEditingMode = false;
}
#override
Widget build(BuildContext context) {
return ListTile(
title: titleWidget,
subtitle: subTitleWidget,
trailing: tralingButton,
);
}
Widget get titleWidget {
return Text(
model.lot
);
}
Widget get subTitleWidget {
if (_isEditingMode) {
_subTitleEditingController = TextEditingController(text: model.quantity);
return TextField(
controller: _subTitleEditingController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
suffix: InkWell(
onTap: () {
_subTitleEditingController.text = '';
},
child: Icon(
Icons.clear,
),
),
)
);
} else {
return Text(
model.quantity.toString();
);
}
}
Widget get tralingButton {
if (_isEditingMode) {
return IconButton(
icon: const Icon(Icons.check),
onPressed: saveChange,
);
} else {
return IconButton(
icon: const Icon(Icons.edit),
onPressed: _toggleMode,
);
}
}
void _toggleMode() {
setState(() {
_isEditingMode = !_isEditingMode;
});
}
void saveChange() {
model.quantity = _subTitleEditingController.text;
_toggleMode();
widget.onChanged(model);
}
}

You are using InkWell inside TextField ,So
First Click is consumed by InkWell
Second Click is consumed by TextField
Problem causing widget
TextField
|_ InkWell
TextField(
controller: _subTitleEditingController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
suffix: InkWell(
onTap: () {
_subTitleEditingController.text = '';
},
child: Icon(
Icons.clear,
),
),
)
);
Solution
Replace InkWell with IconButton.
TextField(
controller: _subTitleEditingController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
suffixIcon: IconButton(
onTap: () {
_subTitleEditingController.text = '';
},
child: Icon(
Icons.clear,
),
),
)
);
Output:

Related

Value change but UI don't

I want to show a button only if the new value is different from the old one. But its not working, the button isn't showing
class ViewPatientPage extends StatefulWidget {
final int status;
final String name;
const ViewPatientPage({required this.status, required this.name, super.key});
#override
State<ViewPatientPage> createState() => _ViewPatientPageState();
}
class _ViewPatientPageState extends State<ViewPatientPage> {
String name = '';
#override
void initState() {
super.initState();
name = widget.name;
}
SizedBox space() {
return const SizedBox(height: 15);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus?.unfocus();
},
child: Scaffold(
appBar: AppBar(
actions: [
(name != widget.name) ? TextButton(
onPressed: () {},
child: const Text(
'Editar',
style: TextStyle(color: Colors.white),
)): const SizedBox.shrink()
],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
children: [
space(),
TextFormField(
initialValue: widget.name,
// keyboardType: keyboardType,
validator: (val) {
if (val.toString().isEmpty || val == null || val == '') {
return 'Fill field';
}
return null;
},
decoration: InputDecoration(
label: const Text('Name'),
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
border: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black),
borderRadius: BorderRadius.circular(20))),
onChanged: (value) {
name = value.trim();
print(name);
print('widget ${widget.name}');
},
// inputFormatters: inputFormatters,
),
space(),
],
),
),
),
);
}
}
#random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
You need to call setState() in onChanged.
Something like:
onChanged: (value) {
setState(() {
name = value.trim();
});
print(name);
print('widget ${widget.name}');
},
Call setState inside onChanged function
setState(() { name = value.trim(); });
To update the UI, you have to a call a setState() in the onChanged: (value) {}
setState(()
{
...
});

Delete Widget at button press Flutter

Recently implemented a tagForm widget at "+" button press, I want to delete those widgets now at "delete" button press, but right now, even when I press the "delete" button, nothing happens.
How can I solve this?
Any help appreciated!
code:
import 'package:flutter/material.dart';
import '../database/firestoreHandler.dart';
import '../models/todo2.dart';
import '../widgets/dialogs.dart';
class TodoEdit extends StatefulWidget {
String? doctitle;
String? doctdescription;
String? docimage;
String? docid;
List? doctags;
TodoEdit({Key? key, this.doctitle, this.doctdescription, this.docimage, this.docid,this.doctags}) : super(key: key);
#override
_TodoEditState createState() => _TodoEditState();
}
class _TodoEditState extends State<TodoEdit> {
final _formKey = GlobalKey<FormState>();
final tcontroller = TextEditingController();
final dcontroller = TextEditingController();
final icontroller = TextEditingController();
var textEditingControllers = <TextEditingController>[];
//-----------------the list where the form is stored----------
var textformFields = <Widget>[];
void _addformWidget(controller) {
setState(() {
textformFields.add(tagForm(controller));
});
}
//------------------------------------------------------------------------
Widget tagForm(controller){
return TextFormField(
controller: controller,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: "Tag",
labelStyle: TextStyle(color: Colors.white60),
fillColor: Colors.black,
filled: true,
suffixIcon: IconButton(
icon:Icon(Icons.delete, color: Colors.white,),
//--------------------- doesn't work?-------------------
onPressed: (){
setState(() {
textformFields.remove(tagForm(controller));
});
},
--------------------------------------------------------------
)
),
);
}
//-----------------------------------------------------------
#override
void initState() {
super.initState();
tcontroller.text = widget.doctitle.toString();
dcontroller.text = widget.doctdescription.toString();
icontroller.text = widget.docimage.toString();
widget.doctags?.forEach((element) {
var textEditingController = new TextEditingController(text: element);
textEditingControllers.add(textEditingController);
//return textformFields.add(tagForm(textEditingController)
return _addformWidget(textEditingController);
//);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[900],
appBar: AppBar(
actions: [
IconButton(onPressed: (){
showDialog(
barrierDismissible: false,
context: context,
builder: (context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
title: Text('Delete TODO'),
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text('Delete'),
onPressed: () {
deleteData(widget.docid.toString(), context);
setState(() {
showSnackBar(context, 'todo "${widget.doctitle}" successfully deleted!');
});
},
),
],
);
},
);
},
icon: Icon(Icons.delete))
],
backgroundColor: Colors.grey[900],
title: Text("${widget.doctitle}"),
),
body: Container(
child: SafeArea(
child: Form(
key: _formKey,
child: Column(
children: [
SizedBox(height: 10),
TextFormField(
controller: tcontroller,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: "Title",
labelStyle: TextStyle(color: Colors.white60),
fillColor: Colors.black,
filled: true,
),
),
SizedBox(height: 10),
TextFormField(
controller: dcontroller,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: "Description",
labelStyle: TextStyle(color: Colors.white60),
fillColor: Colors.black,
filled: true,
),
),
SizedBox(height: 10),
TextFormField(
controller: icontroller,
style: TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: "Image url",
labelStyle: TextStyle(color: Colors.white60),
fillColor: Colors.black,
filled: true,
),
),
SizedBox(height: 10),
Row(children: [
Text("Tags:", style:TextStyle(color: Colors.white)),
IconButton(onPressed: (){
var textEditingController = new TextEditingController(text: "tag");
textEditingControllers.add(textEditingController);
_addformWidget(textEditingController);
print(textformFields.length);
},
icon: Icon(Icons.add,color: Colors.white,),
)
],),
/*SingleChildScrollView(
child: new Column(
children: textformFields,
)
),*/
Expanded(
child: SizedBox(
height: 200.0,
child: ListView.builder(
itemCount: textformFields.length,
itemBuilder: (context,index) {
return textformFields[index];
}),
)
),
],
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
List<String> test = [];
textEditingControllers.forEach((element) {
test.add(element.text);
});
if(tcontroller == '' && dcontroller == '' && icontroller == ''){
print("not valid");
}else{
var todo = Todo2(
title: tcontroller.text,
description: dcontroller.text,
image: icontroller.text,
tags: test,
);
updateData(todo, widget.docid.toString(),context);
setState(() {
showSnackBar(context, 'todo ${widget.doctitle} successfully updated!');
});
}
},
child: Icon(Icons.update),
),
);
}
}
You can't remove anything from the list with objects from tagForm(controller), because these objects are newly created and therefore not the same as in the list (as long as the == operator is not overwritten)
If you still want to have the widgets in a list instead of just storing the controllers and without having to change much, you could remove the widgets like this:
onPressed: (){
setState(() {
controller.dispose();
textEditingControllers.remove(controller);
textformFields.removeWhere((w) => w.controller = controller));
});
},
and change the type of your List: var textformFields = <TextFormField>[]; and of the method TextFormField tagForm(controller).
In general, you can of course optimize the state management, but with this solution it should work for now.
Dont't store Widget, it is bad way. Insteads store there property, render by List then remove by index when you need.
ps: some code syntax can wrong, i write this on browser.
class _TodoEditState extends State<TodoEdit> {
...
var textformFields = <String>[];
...
void _addformWidget([String? initValue]) {
setState(() => textformFields.add(initValue ?? ""));
}
...
Widget tagForm(String value, void Function(String) onChange, void Function() onRemove){
var openEditor = () {
// Open dialog with text field to edit from [value] call onChange with
// new value
OpenDialog().then((newvalue) {
if(newvalue != null) onChange(newvalue);
}
};
var delete = () {
// Open confirm dialog then remove
OpenConfirmDialog("your message").then((continue) {
if(continue) onRemove();
});
};
return InkWell(
onTap: openEditor,
child: Text(value), // render your tag value
);
}
...
#override
void initState() {
...
textformFields = List.filled(widget.doctags ?? 0, ""); // or List.generate/map if you want replace by own value.
}
...
#override
Widget build(BuildContext context) {
...
ListView.builder(
itemCount: textformFields.length,
itemBuilder: (context,index) => tagForm(
textformFields[index],
(newvalue) => setState(() => textformFields[index] = newvalue),
() => setState(() => textformFields = textformFields..removeAt(index));,
),
),
...
);
}

Pushing(sending) a single variable to new Class with multiple arguments

I'm trying to push kombiController.text value to a new class, but after trying all sorts of things, I just don't know what else to do. I'm getting an error "8 positional arguments expected, but found 0, which I'm guessing is due to my FeedbackForm class requiring 8, but I don't find solution to how to fix it or set up some default paramaters.
Note: The other variable message-ekipaController is getting sent normally and I had no troubles implementing it.
I've been trying to fix and find a solution to this "little" problem for 2 days now and no matter from which angle I try to resolve this issue, I just get new problems somewhere else.
mainscreen.dart
import 'package:flutter/material.dart';
import 'secondscreen.dart';
import 'models/feedback_form.dart';
import 'package:flutter/widgets.dart';
class MainScreen extends StatefulWidget {
MainScreen({Key key}) : super(key: key);
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
String kombi;
String valuePolja;
TextEditingController ekipaController = new TextEditingController();
TextEditingController kombiController = new TextEditingController();
// Default Drop Down Item.
String dropdownValue = 'Tom Cruise';
// To show Selected Item in Text.
String holder = '' ;
List listItem = [
"1", "2", "3", "4", "5"
];
void getDropDownItem(){
setState(() {
holder = dropdownValue ;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("DHL"),
),
body: Center(
child: Column(
children: <Widget> [
TextFormField(
controller: ekipaController,
onChanged: (text) {
valuePolja = text;
},
validator: (value){
if(value.isEmpty){
return "Enter Valid Name";
}else{
return null;
}
},
decoration: InputDecoration(
hintText: "Ime Ekipe",
),
),
DropdownButton(
hint: Text("Izberi Kombi: "),
dropdownColor: Colors.white,
icon: Icon(Icons.arrow_drop_down),
value: kombi,
onChanged: (newValue) {
setState(() {
kombi = newValue;
});
kombiController.text = newValue;
},
items: listItem.map((valueItem) {
return DropdownMenuItem(
value: valueItem,
child: Text(valueItem),
);
}).toList(),
),
Padding(
padding: EdgeInsets.only(top: 30, bottom: 30),
child :
//Printing Item on Text Widget
Text('Selected Item = ' + kombiController.text,
style: TextStyle (fontSize: 22, color: Colors.black))),
ElevatedButton(
child: Text('Next Screen',
style: TextStyle(
color: Colors.white,
),
),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => SecondScreen(message: ekipaController.text)));
Navigator.push(context,
MaterialPageRoute(builder: (context) => FeedbackForm(kombiController: kombiController.text)));
}
),
],
),
)
);
}
}
feedback_form.dart
import 'package:dhl_app/mainscreen.dart';
import 'package:flutter/widgets.dart';
class FeedbackForm extends StatefulWidget {
const FeedbackForm(this.ime_ekipe, this.ime_stranke, this.postna_stevilka, this.teza, this.type, this.datum, this.dostavljeno, this.kombiController);
final String kombiController;
final String ime_ekipe;
final String ime_stranke;
final String postna_stevilka;
final String teza;
final String type;
final String datum;
final String dostavljeno;
//FeedbackForm(this._ime_ekipe, this._ime_stranke, this._postna_stevilka, this._teza, this._type, this._datum, this._dostavljeno, this.kombiController);
String toParams() => "?ime_ekipe=$ime_ekipe&ime_stranke=$ime_stranke&postna_stevilka=$postna_stevilka&teza=$teza&type=$type&datum=$datum&dostavljeno=$dostavljeno&kombi=$kombiController";
#override
State<StatefulWidget> createState() {
// TODO: implement createState
throw UnimplementedError();
}
}
secondscreen.dart (where the other variable works)
import 'package:flutter/material.dart';
import 'models/controller.dart';
import 'models/feedback_form.dart';
import 'mainscreen.dart';
class SecondScreen extends StatefulWidget {
final String message;
const SecondScreen({Key key, this.message}) : super(key: key);
#override
_SecondScreen createState() => _SecondScreen();
}
class _SecondScreen extends State<SecondScreen> {
var _controller = TextEditingController();
final _formKey = GlobalKey<FormState>();
final _scaffoldKey = GlobalKey<ScaffoldState>();
//TextField Controllers
TextEditingController nameController = TextEditingController();
TextEditingController emailController = TextEditingController();
TextEditingController phoneNumberController = TextEditingController();
TextEditingController feedbackController = TextEditingController();
TextEditingController typeController = TextEditingController();
TextEditingController dateController = TextEditingController();
TextEditingController dostavljenoController = TextEditingController();
TextEditingController kombiController = TextEditingController();
void _submitForm(){
if(_formKey.currentState.validate()) {
FeedbackForm feedbackForm = FeedbackForm(
nameController.text,
emailController.text,
phoneNumberController.text,
feedbackController.text,
typeController.text,
dateController.text,
dostavljenoController.text,
kombiController.text,
);
FormController formController = FormController(
(String response) {
print(response);
if(response == FormController.STATUS_SUCCESS){
_showSnackBar("Feedback Submitted!");
}else {
_showSnackBar("Error Occured!");
}
});
_showSnackBar("Submitting Feedback");
formController.submitForm(feedbackForm);
}
}
_showSnackBar(String message) {
final snackBar = SnackBar(content: Text(message),);
_scaffoldKey.currentState.showSnackBar(snackBar);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Vnesi Podatke"),
),
key: _scaffoldKey,
body: Form(
key: _formKey,
child: Container(
padding: EdgeInsets.symmetric(vertical: 50, horizontal: 24),
child: Column(
children: <Widget> [
TextFormField(
controller: nameController,
validator: (value){
if(value.isEmpty){
return "Enter Valid Name";
}else{
return null;
}
},
decoration: InputDecoration(
hintText: "${widget.message}",
suffixIcon: IconButton(
onPressed: () => nameController.clear(),
icon: Icon(Icons.clear),
),
),
),
TextFormField(
controller: emailController,
validator: (value){
if(value.isEmpty){
return "Enter Valid Email";
}else{
return null;
}
},
decoration: InputDecoration(
hintText: "Ime Stranke",
suffixIcon: IconButton(
onPressed: () => emailController.clear(),
icon: Icon(Icons.clear),
),
),
),
TextFormField(
controller: phoneNumberController,
validator: (value){
if(value.isEmpty){
return "Enter Valid Mobile Number";
}else{
return null;
}
},
decoration: InputDecoration(
hintText: "Poštna Številka",
suffixIcon: IconButton(
onPressed: () => phoneNumberController.clear(),
icon: Icon(Icons.clear),
),
),
),
TextFormField(
controller: feedbackController,
validator: (value){
if(value.isEmpty){
return "Enter Valid Feedback";
}else{
return null;
}
},
decoration: InputDecoration(
hintText: "Kilogramov",
suffixIcon: IconButton(
onPressed: () => feedbackController.clear(),
icon: Icon(Icons.clear),
),
),
),
TextFormField(
controller: typeController,
validator: (value){
if(value.isEmpty){
return "Enter Valid Feedback";
}else{
return null;
}
},
decoration: InputDecoration(
hintText: "Tip",
suffixIcon: IconButton(
onPressed: () => typeController.clear(),
icon: Icon(Icons.clear),
),
),
),
TextFormField(
controller: dateController,
validator: (value){
if(value.isEmpty){
return "Enter Valid Feedback";
}else{
return null;
}
},
decoration: InputDecoration(
hintText: "Datum",
suffixIcon: IconButton(
onPressed: () => dateController.clear(),
icon: Icon(Icons.clear),
),
),
),
TextFormField(
controller: dostavljenoController,
validator: (value){
if(value.isEmpty){
return "Enter Valid Feedback";
}else{
return null;
}
},
decoration: InputDecoration(
hintText: "Dostavljeno",
suffixIcon: IconButton(
onPressed: () => dostavljenoController.clear(),
icon: Icon(Icons.clear),
),
),
),
RaisedButton(
color: Colors.blue,
textColor: Colors.white,
onPressed: _submitForm,
child: Text('Submit Feedback'),
)
],
)
),
)
);
}
}

Flutter: How to dynamically hide an action in the AppBar?

I've got search input as title and clear button as actions in my AppBar:
AppBar(
actions: <Widget>[IconButton(icon: Icon(Icons.clear), onPressed: () => queryController.clear())],
title: TextField(
autofocus: true,
controller: queryController,
decoration: InputDecoration(
hintText: 'Search...',
),
),
),
How can I dynamically display a clear button depending on search field value?
Just add if (queryController.text.length > 0) before action you want to hide dynamically and add setState to onChanged method of the TextField, here is your code;
AppBar(
actions: <Widget>[
if (queryController.text.length > 0)
IconButton(
icon: Icon(Icons.clear),
onPressed: () => queryController.clear(),
)
],
title: TextField(
autofocus: true,
controller: queryController,
decoration: InputDecoration(
hintText: 'Search...',
),
onChanged: (value) {
setState(() {});
},
),
)
inside the StatefulWidget you can use setState and a bool field to track if the action is shown or not:
bool _shouldHideAction;
TextEditingController _textEditingController;
#override
void initState() {
_shouldHideAction = true;
_textEditingController = TextEditingController();
super.initState();
}
#override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: _shouldHideAction ? [] : [Icon(Icons.clear)],
title: Text('appbarTitle'),
),
body: TextField(
style: TextStyle(color: Colors.red),
controller: _textEditingController,
onChanged: (newValue) {
if (newValue.length > 0) {
setState(() {
_shouldHideAction = false;
});
} else {
setState(() {
_shouldHideAction = true;
});
}
},
),
);
}

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