TextButton is automatically invoked when the dropDown menu item is changed - flutter

Here goes my code ,
I am using the TextButton to update the order ,But after every change in the dropdown item, onPress is automatically invoked and the function updateOrder is automatically invoked
import 'package:admin/constants/Constants.dart';
import 'package:admin/model/order_model.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
class DetailsScreen extends StatefulWidget {
const DetailsScreen({Key? key}) : super(key: key);
#override
State<DetailsScreen> createState() => _DetailsScreenState();
}
class _DetailsScreenState extends State<DetailsScreen> {
List<String> _dropDownQuantities = [Pending, Confirmed, Rejected, Success];
late OrderModel order;
late String selectedStatus = order.status;
#override
Widget build(BuildContext context) {
order = ModalRoute.of(context)?.settings.arguments as OrderModel;
return Scaffold(
appBar: AppBar(
actions: [],
title: Text("Order Details"),
),
body: Column(children: [
Text(order.id),
DropdownButtonHideUnderline(
child: DropdownButton2<String>(
value: selectedStatus,
items: _dropDownQuantities
.map((e) => DropdownMenuItem<String>(
child: Text(e),
value: e,
))
.toList(),
onChanged: (value) {
setState(() {
selectedStatus = value;
});
},
)),
TextButton(onPressed: updateOrder(order, selectedStatus), child: Text("Confirm")),
]));
}
}
updateOrder(OrderModel order, String selected) {
print("I am executed");
}
So whenever i change the dropDown menu,
I am executed is printed in the console.
Edit:
But when i used the container with InkWell it was working fine. Why not working with TextButton ?

You are directly calling the method on build, you can create an inline anonymous function to handle this.
TextButton(
onPressed: ()=> updateOrder(order, selectedStatus),
child: Text("Confirm")),
onPressed Called when the button is tapped or otherwise activated.
While we use onPressed:method() call on every build, on dropDown onChanged we use setState, and it rebuilds the UI and onPressed:method() call again.
What we need here is to pass a function(VoidCallback) that will trigger while we tap on the button. We provide it like,
onPressed:(){
myMethod();
}
More about TextButton.

Related

Update child from parent in Flutter

Currently, I have two sample files Parent.dart and Child.dart.
In Parent.dart file this is what the code is like:
Parent.dart file:
children:
[
isDisabled
? Icon(Icons.public, color: Colors.grey)
: Icon(Icons.public, color:Colors.white),
InkWell(
onTap:()=> Navigator.push(context,MaterialPageRoute(builder: (context)=> Child(
isDisabled: isDisabled, function: ()=> function())),
]
function()
{
setState(()=> isDisabled = !isDisabled);
}
and in Child.dart the code is something like this:
children:
[
widget.isDisabled
? Icon(Icons.public, color: Colors.grey)
: Icon(Icons.public, color:Colors.white),
InkWell(
onTap:()=> widget.function(),
]
I have some data being fetched from a server that is used to populate a list of cards inside listview.builder.
What I'm trying to do is inherent variables from the parent and use their value to update the child. Currently, if I run this parent does change, but the child doesn't until you navigate back from parent to child.
For a better context: Imagine a list of cards. Each has an add-to-list button. Now if you click on the card it goes to another screen "child.dart" where it gives you more details about the item on the card you clicked. Now if you click the add-to-list button on the child screen it should also update the parent.
I tried different ways of achieving this "UI synchrony" for a better user experience. But I didn't find a proper way to implement it.
Things I tried: Provider (but it updates all the items on the list instead of each instance.),
a "hacky" method of editing the data in the list on the client side and updating the widget based on that. (This technique does work, but ewwwww)
I'm not really sure to understand your question.
If your question is how trigger a function in parent from child screen, here is your answer.
I made a working example. I think you were really close.
Another option for state management is riverpod 2.0
Or you can pass value in Navigator.pop and trigger the function in parent.
Parent model
class Parent {
String title;
bool isDisabled = false;
Parent({required this.title});
}
Main.dart
import 'package:flutter/material.dart';
import 'parent.dart';
import 'ParentCard.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({super.key});
List<Parent> parentList = [Parent(title: 'Item 1'), Parent(title: 'Item 2')];
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: ListView.builder(
itemCount: parentList.length,
itemBuilder: (BuildContext context, int position) {
return ParentCard(title: parentList[position].title);
},
),
),
);
}
}
ParentCard.dart
import 'package:flutter/material.dart';
import 'child.dart';
class ParentCard extends StatefulWidget {
String title;
ParentCard({super.key, required this.title});
#override
State<ParentCard> createState() => _ParentCardState();
}
class _ParentCardState extends State<ParentCard> {
bool isDisabled = false;
#override
Widget build(BuildContext context) {
return Row(
children: [
Text(widget.title),
isDisabled
? Icon(Icons.public, color: Colors.green)
: Icon(Icons.public, color: Colors.black),
IconButton(
icon: Icon(Icons.plus_one),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ChildCard(isDisabled: isDisabled, handler: handler)),
),
)
],
);
}
handler() {
setState(() => isDisabled = !isDisabled);
}
}
** child.dart**
import 'package:flutter/material.dart';
class ChildCard extends StatefulWidget {
VoidCallback handler;
bool isDisabled;
ChildCard({super.key, required this.isDisabled, required this.handler});
#override
State<ChildCard> createState() => _ChildCardState();
}
class _ChildCardState extends State<ChildCard> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
const Text('child !'),
widget.isDisabled
? const Icon(Icons.public, color: Colors.green)
: const Icon(Icons.public, color: Colors.black),
ElevatedButton(
onPressed: () {
setState(() {
widget.isDisabled = !widget.isDisabled;
});
widget.handler();
},
child: const Text('click to trigger'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('pop it'),
)
]),
);
}
}

is it possible to know trigger checkbox or item text when using CheckboxListTile in flutter

I am using CheckboxListTile to show some todo item in flutter(v3.0.4). This is the code looks like:
CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
title: Text(element.name,style:element.isCompleted == 1? TextStyle(color: Colors.grey):TextStyle(color: Colors.black)),
value: element.isCompleted == 1?true:false,
checkColor: Colors.green,
selected: element.isCompleted == 1?true:false,
onChanged: (bool? value) {
if(value!){
element.isCompleted = 1;
}else{
element.isCompleted = 0;
}
TodoProvider.updateTodo(element).then((value) => {
TodoProvider.getTodos().then((todos) => {
buildTodoItems(todos)
})
});
},
))
when the user tap the CheckboxListTile item text, I want to show the todo detail information, when the user tap the checkbox, I want to make the todo task changed to complete. Now I am facing a problem is that I could not detect which part the user tap, all the way will trigger onchange event. I have already read the CheckboxListTile source code, seems no api to do this. Am I misssing something? what should I do to detect which part the user select?
You can wrap your title in a GestureDetector(). Now when the title is tapped, only the gesture detector will be run, and not the onChanged().
In this example, if you tap on the text "Checkbox" then you can see the actual checkbox value is not being updated but the GestureDetector is being called, and if you look at the console "tapped" is being printed.
Here is a complete example. I hope you understand:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _value = false;
#override
Widget build(BuildContext context) {
return CheckboxListTile(
title: GestureDetector(
child: Text('Checkbox'),
onTap: () {
print('tapped');
// you can change the value here too
// setState(() {
// _value = !_value;
// });
},
),
value: _value,
onChanged: (bool? value) {
setState(() {
_value = value!;
});
},
);
;
}
}

Flutter Keyboard show and immediately disappeared at textfied in listview

I have a list view and want to edit the Tile's title. When user click the edit icon, text widget change to TextField. Once user tap the textfield, keyboard show and immediately disappeared.
May I know what is the issue?
class EditableListTile extends StatefulWidget {
final Favourite favourite;
final Function onChanged;
final Function onTap;
const EditableListTile(
{Key? key,
required this.favourite,
required this.onChanged,
required this.onTap})
: super(key: key);
#override
_EditableListTileState createState() => _EditableListTileState();
}
class _EditableListTileState extends State<EditableListTile> {
Favourite? favourite;
late bool _isEditingMode;
late TextEditingController _titleEditingController;
#override
void initState() {
super.initState();
favourite = widget.favourite;
_isEditingMode = false;
}
#override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
widget.onTap(favourite);
},
leading: leadingWidget,
title: titleWidget,
trailing: tralingButton,
);
}
Widget get leadingWidget {
return SizedBox(
width: 32,
child: FolderIcon(
color: Theme.of(context).iconTheme.color!,
),
);
}
Widget get titleWidget {
if (_isEditingMode) {
_titleEditingController = TextEditingController(text: favourite?.name);
return TextField(
controller: _titleEditingController,
);
} else {
return Text(favourite!.name);
}
}
Widget get tralingButton {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
(favourite?.isDefault == false)
? (_isEditingMode
? IconButton(
icon: const Icon(Icons.check),
onPressed: saveChange,
)
: IconButton(
icon: const Icon(Icons.edit),
onPressed: _toggleMode,
))
: Container(),
_isEditingMode
? IconButton(
icon: const Icon(Icons.cancel_outlined),
onPressed: cancelChange,
)
: Container()
],
);
}
void _toggleMode() {
setState(() {
_isEditingMode = !_isEditingMode;
});
}
void cancelChange() {
setState(() {
_isEditingMode = !_isEditingMode;
});
}
void saveChange() {
favourite!.name = _titleEditingController.text;
_toggleMode();
widget.onChanged(favourite!);
}
}
you get this error because you initialized the TextEdittingController inside the titleWidget. every time the widget rebuild, create a new instance of TextEdittingController.
on top of your clas change it like this
// late TextEditingController _titleEditingController; <== Change This
TextEditingController _titleEditingController = TextEditingController();
in titleWidget change your code to this.
Widget get titleWidget {
if (_isEditingMode) {
_titleEditingController.text = favourite?.name;
Real culprit is key in ListView.seperate. I used key : UniqueKey(). If I change to ValueKey(state.favourites[index]), it is working now.
I used key : UniqueKey() because I have onDismissed but one of the item, want to trigger onDismissed but don't want to dismissed.
Let's say, item is folder name and if user delete the item, delete the folder and delete all the files under that folder. But one folder is system generated and don't want user to delete that folder but let them to delete files. So we call confirmDismiss and tell the user that it is system generated folder and only will delete files.
But list view don't allow. So I found out the UniqueKey is work
around. So edit is importance. So that I take out UniqueKey.
Like Alex Aung's answer above, they're right that the use of UniqueKey() is a problem .
In my case I had an action button that pushed a page route to the navigator. On the content view (GameView) I had a UniqueKey() set and it was responsible for series of issues with input fields downstream.
Any time I set this back to UniqueKey(), any clicking inside a downstream TextFormField causes the Keyboard open then immediately close.
Widget getActionButton() {
return FloatingActionButton(
onPressed: () {
final Account account = Account("test#test.com");
widget.viewModels.gamesModel.load(account);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GamesView(
key: Key("GameView"), // UniqueKey() is a problem here.
account: account,
viewModels: widget.viewModels,
)
)
);
},
tooltip: 'Save Changes',
child: Icon(Icons.save),
);
}

Flutter: Select an icon on an AlertDialog and show the new icon

I am trying to use an AlertDialog in flutter where the user can press a card to select an icon. Once they've selected the icon, the AlertDialog should show the newly selected icon.
Right now, I have it so that every time the user taps on the card, the card gets reloaded. However, this means that if I select an icon, I need to tap to select a second icon before it gets reloaded with my previous change.
All advice and/or different ideas on how to handle something like this are welcome.
Here is the code I have below:
import 'package:flutter/material.dart';
import 'package:test_003/data/dataStoreLegendItems.dart'; //has defaultIcon which should get updated
import 'package:test_003/dialogs/iconPickerDialog.dart';
class IconPickerCard extends StatefulWidget {
var alertDialogContext;
IconPickerCard({this.alertDialogContext});
#override
_IconPickerCardState createState() => _IconPickerCardState();
}
class _IconPickerCardState extends State<IconPickerCard> {
#override
Widget build(BuildContext context) {
return new Card(
child: ListTile(
leading: legendItems.defaultIcon,
title: Text('Select Icon'),
onTap: () async {
setState(() {
print("First line of IconPickerCard set state");
showIconPickerDialog(widget.alertDialogContext);
print('Icon Picker Card List Tile pressed');
});
},
),
);
}
}
This gets called by the Alert Dialog:
import 'package:flutter/material.dart';
import 'package:test_003/components/iconPickerForPopup.dart';
class ReuseAddPopup extends StatefulWidget {
#override
_ReuseAddPopupState createState() => _ReuseAddPopupState();
}
class _ReuseAddPopupState extends State<ReuseAddPopup> {
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Column(
children: [
IconPickerCard(alertDialogContext: context),
],
),
);
}
}
This is what it looks like:
Alert Dialog
Then when the card is pressed:
IconPicker
After an icon is selected the changes do not get reflected on the card until the card is pressed again:
After Icon is selected
The context is different in overlay widgets such as modalBottomSheet and showDialog so the state does not rebuild, to make rebuild we have to wrap it with a StatefulBuilder widget like so:-
#override
Widget build(BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter dialogState /*You can rename this!*/) {
return Card(
child: ListTile(
leading: legendItems.defaultIcon,
title: Text('Select Icon'),
onTap: () async {
dialogState(() {
print("First line of IconPickerCard set state");
showIconPickerDialog(widget.alertDialogContext);
print('Icon Picker Card List Tile pressed');
});
},
),
);
}
});
would recommend using this in the call to AlertDialog and make everything stateless.

Flutter-web TextFormField issue

usually I can disable/grey-out a button until a TextFormField meets certain parameters in flutter by something like this:
TextFormField(
controller: _controller
value: (value)
)
SubmitButton(
onPressed: _controller.text.isNotEmpty ? _submit : null;
)
But when compiled as a website the Button seems no longer aware of the controller value...
I have tried targeting in several different ways, e.g. _controller.value.text.isEmpty and _controller.text.isEmpty...
I'm guessing I'm missing something or this method just isn't possible for web ... Is there any other way to get the same result?
To be honest, your code shouldn't work in flutter mobile either, but may be works because of screen keyboard causes widget rebuild when showing or hiding.
To fix this issue we have to use stateful widget with state variable like canSubmit and update it in textField's listener onChange with setState method. Then every time the text changes, our stateful widget will update the submit button..
class Page extends StatefulWidget {
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
bool canSubmit;
#override
void initState() {
canSubmit = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
onChanged: (value) {
setState(() {
canSubmit = value.isNotEmpty;
});
},
),
RaisedButton(
onPressed: canSubmit ? _submit : null,
child: Text('Submit'),
)
],
),
),
);
}
void _submit() {
print('Submitted');
}
}