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

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

Related

TextField not accept text on first click 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:

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

Clear textfield based onchanged (flutter)

TextField(
inputFormatters: [
new FilteringTextInputFormatter.allow(
RegExp('[0-9]')),
],
hintText: 'some text'
title: '',
editingController: controllers[31],
value: somenumber,
onChange: (value) {
if (num.parse(value) <= 3000 &&
num.parse(value) >= 30) {
// save some data
}else{
controllers[31].clear(),
},
),
so above is a textfield with onchange, right now the textfield does not clear if i put outside of the range, is it possible to clear the textfield based onchange?
Is the TextField() you are using part of a plugin? The properties look a bit odd compared with the latest stable release of Flutter.
Here's an example that clears the text:
TextEditingController textEditingController = new TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: TextField(
inputFormatters: [
new FilteringTextInputFormatter.allow(RegExp('[0-9]')),
],
decoration: InputDecoration(
hintText: 'some text',
),
controller: textEditingController,
onChanged: (value) {
if (value.length <= 10) {
// something
} else {
textEditingController.clear();
}
}
),
),
);
}
Create TextEditingController for your text field and then assign it to the controller property of TextField.
// create controller
TextEditingController _controller = new TextEditingController();
// assign it to TextField controller property
TextField(
controller : _controller
// your other properties
)
then to clear the text
// clear text
_controller.clear();
``
U can also do this :
TextEditingController textEditingController = new TextEditingController();
Function onchange;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: TextField(
inputFormatters: [
new FilteringTextInputFormatter.allow(RegExp('[0-9]')),
],
decoration: InputDecoration(
hintText: 'some text',
suffixIcon: IconButton(
onPressed: () {
widget.onchanged('');
controller.clear();
},
),
controller: textEditingController,
onChanged: (value) => onchange(value)
}
),
),
);
}

Make the text editable after press the button

Is anyone know how to make the text editable after I press the button? I want it to be editable only when the user clicks the button. Below is the result that I needed.
If I want to edit the text "Festive Leave", then I need to click the edit button.
I think this should do what you want.
TextEditingController _controller =
TextEditingController(text: "Festive Leave");
bool _isEnable = false;
//These are initialize at the top
Row(
children: <Widget>[
Container(
width: 100,
child: TextField(
controller: _controller,
enabled: _isEnable,
),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
setState(() {
_isEnable = true;
});
})
],
),
Row(
children: <Widget>[
Container(
width: 100,
child: TextField(
decoration: InputDecoration(
hintText: "Festive Leave",
hintStyle: TextStyle(color: Colors.black),
),
enabled: _isEnable,
),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
setState(() {
_isEnable = true;
});
})
],
),
your choice this one the text will go away as soon as the user starts to type though
I think this will help you
class SampleDemo extends StatefulWidget {
#override
_SampleDemoState createState() => _SampleDemoState();
}
class _SampleDemoState extends State<SampleDemo> {
String title = "MyTitle";
bool isEditable=false;
#override
Widget build(BuildContext context) {
return Row(children: [
Expanded(
child: !isEditable
? Text(title)
: TextFormField(
initialValue: title,
textInputAction: TextInputAction.done,
onFieldSubmitted: (value) {
setState(() => {isEditable = false, title = value});
})),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
setState(() => {
isEditable = true,
});
},
)
]);
}
}