So I am struggling with the DropdownButtonFormField where when you change the value it runs the onChange function with the updated value. However, once the onChange finishes the value variable seems to reset itself meaning it never changes.
This is a cut-down version of the full form:
final _formKey = GlobalKey<FormState>();
TextEditingController assetGroupNameController = new TextEditingController();
TextEditingController assetGroupDescriptionController = new TextEditingController();
String assetGroupTypeController;
Widget build(BuildContext context) {
ProgressDialog pr;
assetGroupNameController.text = widget.assetGroup.name;
assetGroupDescriptionController.text = widget.assetGroup.description;
assetGroupTypeController = widget.assetGroup.type;
return ListView(
children: <Widget>[
Card(
elevation: 13.0,
child: Form(
key: _formKey,
child: DropdownButtonFormField(
value: assetGroupTypeController,
items: assetGroupTypes.map((f) {
return new DropdownMenuItem<String>(
value: f['key'],
child: new Text(f['text']),
);
}).toList(),
onChanged: (value) {
typeDropdownChange(value);
})
)
)
);
}
void typeDropdownChange(value) {
setState(() {
assetGroupTypeController = value;
});
}
You assigned the controller directly to value parameter of DropdownButtonFormField and you have string value for DropdownMenuItem. You should be storing the same data type value. Check below example and modify your code accordingly
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Material(
child: Center(
child: new MyDropDown(),
),
),
);
}
}
class MyDropDown extends StatefulWidget {
const MyDropDown({
Key key,
}) : super(key: key);
#override
_MyDropDownState createState() => _MyDropDownState();
}
class _MyDropDownState extends State<MyDropDown> {
String selected;
#override
Widget build(BuildContext context) {
return DropdownButtonFormField<String>(
value: selected,
items: ["Item 1", "Item 2", "Item 3"]
.map((label) => DropdownMenuItem<String>(
child: Text(label),
value: label,
))
.toList(),
onChanged: (value) {
setState(() => selected = value);
},
);
}
}
Related
I want to autofill several textfields with one suggestion, like for example: If I select Washington as a state where I live I want the other field that would be country field to fill itself with U.S.
Thanks for your attention!
You will need to use setState( ) inside the onChanged. inside that setState, you will change the value of the other field otherDropdownValue . Here is a small example with dropDownMenus.
Dont forget you need a StatefulWidget (not StateLess)
Code:
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
String dropdownValue = 'One';
String otherDropdownValue = 'Two';
#override Widget build(BuildContext context) {
return Column(children: [
DropdownButton<String>(
value: dropdownValue,
onChanged: (String? newValue) {
//******************************************
//*****Here is what you are looking for*****
//******************************************
setState(() {
dropdownValue = newValue;
otherDropdownValue = newValue; ///Changes the other one
});
},
items: <String>['One', 'Two', 'Free', 'Four'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(value: value, child: Text(value),);}).toList(),
),
DropdownButton<String>(
value: otherDropdownValue,
onChanged: (String? newValue) {
setState(() {
otherDropdownValue = newValue;
});
},
items: <String>['One', 'Two', 'Free', 'Four'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(value: value, child: Text(value),);}).toList(),
),
],
);
}
}
Let me know if this does not help?
EDIT to answer your last comment:
Same logic to apply with a TextField or a textformfield.
You will need to add a TextEditingController() to control the text displayed.
Below is a fully working example (the part you need to look at is at the end)
and here is a link that explains the code (note I adjusted the code for your specific use case)
https://flutter.dev/docs/cookbook/forms/text-field-changes
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Retrieve Text Input',
home: MyCustomForm(),
);
}
}
// Define a custom Form widget.
class MyCustomForm extends StatefulWidget {
const MyCustomForm({Key? key}) : super(key: key);
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
// Define a corresponding State class.
// This class holds data related to the Form.
class _MyCustomFormState extends State<MyCustomForm> {
// Create a text controller and use it to retrieve the current value
// of the TextField.
final myController = TextEditingController();
#override
void initState() {
super.initState();
// Start listening to changes.
myController.addListener(_printLatestValue);
}
#override
void dispose() {
// Clean up the controller when the widget is removed from the widget tree.
// This also removes the _printLatestValue listener.
myController.dispose();
super.dispose();
}
void _printLatestValue() {
print('Second text field: ${myController.text}');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Retrieve Text Input'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
///********************
///**** LOOK HERE ****
///********************
TextField(
onChanged: (text) {
myController.text = text;
},
),
TextField(
controller: myController,
),
],
),
),
);
}
}
I have a listview.builder in flutter and every item of the list has a dropdown now whenever I select one dropdown value of every dropdown changes. how can I fix this problem in flutter?
Ok, after spending a couple of hours on this and not finding a satisfactory answer (but a lot of hints) I worked it out.
I made a new StatefulWidget class that wraps the DropdownButton. It is instantiated with the List of items for the dropdown.
listview_dropdownbutton.dart
import 'package:flutter/material.dart';
class ListviewDropdownButton extends StatefulWidget {
final List<dynamic> sizes;
const ListviewDropdownButton({
Key? key,
required this.sizes,
}) : super(key: key);
#override
State<ListviewDropdownButton> createState() => _ListviewDropdownButton();
}
class _ListviewDropdownButton extends State<ListviewDropdownButton> {
List<dynamic>? _sizes;
String _currentSize = '';
#override
Widget build(BuildContext context) {
_sizes = _sizes ?? widget.sizes;
_currentSize = _currentSize != '' ? _currentSize : widget.sizes[0];
return DropdownButton<dynamic>(
value: _currentSize,
style: const TextStyle(
color: Colors.green,
),
items: _sizes!.map<DropdownMenuItem<dynamic>>((dynamic size) {
return DropdownMenuItem(
value: size,
child: Text(size),
);
}).toList(),
onChanged: (dynamic size) {
if (_currentSize != size) {
setState(() {
_currentSize = size!;
});
}
},
);
}
}
In the parent widget, just include the class and use it where you'd put the DropdownButton.
Here's a working example.
main.dart
import 'package:flutter/material.dart';
import 'listview_dropdownbutton.dart';
void main() => runApp(const DropdownButtonApp());
class DropdownButtonApp extends StatelessWidget {
const DropdownButtonApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('DropdownButton In ListView')),
body: Center(
child: DropdownButtonExample(),
),
),
);
}
}
class DropdownButtonExample extends StatelessWidget {
DropdownButtonExample({super.key});
final List<String> _items = <String>['Shirt', 'T-Shirt', 'Pants', 'Blouse', 'Coat'];
final List<String> _sizes = <String>['Small', 'Medium', 'Large', 'X-Large'];
String _currentSize = 'Small';
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _items.length,
itemBuilder: (
BuildContext context,
int index,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_items[index]),
Row(
children: [
ListviewDropdownButton(
sizes: _sizes,
),
DropdownButton<String>(
value: _currentSize,
style: const TextStyle(
color: Colors.red,
),
items: _sizes.map<DropdownMenuItem<String>>((String size) {
return DropdownMenuItem(
value: size,
child: Text(size),
);
}).toList(),
onChanged: (String? size) {
if (_currentSize != size) {
// setState(() {
_currentSize = size!;
// });
}
},
),
],
),
const Divider(
thickness: 2,
height: 2,
),
],
);
},
);
}
}
To illustrate it works, I put both the ListviewDropdownButton and a regular DropdownButton in the ListView.
I added String _currentSize = 'Small'; and the onChanged method to show the regular DropdownButton does not work. It never changes from "Small", which was my original problem.
I have a widget who need to select a single item using Radio as dynamically. I already created that widget like below:
int number;
return Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Radio(
value: index,
groupValue: number,
activeColor: Color(0xFFE91E63),
onChanged: (int val) {
setState(() {
number = val;
print('Show the Resumes $number');
});
},
),
Text(
'Show',
),
],
),
);
I looping the above widget inside a ListView.builder. And the index in the value is from index from itemBuilder on ListView.builder. And when I run the code, it looks like this.
So how to make my Radio is only select a single item?
Maybe you can write like this, declare value and groupValue outside the loop (builder in ListView). And create value, groupValue, and onChanged in the constructor. And the result like this.
...
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<int> _numbers = List<int>.generate(5, (index) => index);
int _groupNumber;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return HomeContent(
value: _numbers[index],
groupValue: _groupNumber,
onChanged: (int value) {
setState(() {
_groupNumber = value;
});
},
);
},
itemCount: _numbers.length,
),
);
}
}
class HomeContent extends StatelessWidget {
final int value;
final int groupValue;
final ValueChanged<int> onChanged;
const HomeContent({
Key key,
this.value,
this.groupValue,
this.onChanged,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return RadioListTile<int>(
value: this.value,
groupValue: this.groupValue,
onChanged: this.onChanged,
title: Text('Value $value On Group $groupValue'),
);
}
}
Change 'groupValue: number' to
final var _groupValue = -1;
return Container(
...
groupValue: _groupValue,
...
);
and show Trouble with flutter radio button
in this below code when i try change item on DropDownMenu, selected item don't change and selected item is first item of list
for example after selecting bbbbbbb i have aaaaaaa or selecting ccccccc i have aaaaaaa
import 'package:flutter/material.dart';
void main()=>runApp(
MaterialApp(
home: _MyApp(),
),
);
class _MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState()=>_MyAppState();
}
class _MyAppState extends State<_MyApp> {
SessionsEntity sessionData;
#override
Widget build(BuildContext context) {
List<DropdownMenuItem<SessionsEntity>> _dropdownMenuItems;
_dropdownMenuItems = buildDropdownMenuItems();
sessionData = _dropdownMenuItems[0].value;
return Scaffold(
body: DropdownButtonHideUnderline(
child: Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.white,
),
child: Container(
child: Center(
child: DropdownButton(
items: _dropdownMenuItems,
isDense: true,
value: sessionData,
onChanged: onChangeDropdownItem,
isExpanded: true,
hint: Text('please select item'),
),
),
),
),
),
);
}
List<DropdownMenuItem<SessionsEntity>> buildDropdownMenuItems() {
List<SessionsEntity> sessions = [
SessionsEntity(1, 'aaaaaaa', 1, 'a-a-a-a-a'),
SessionsEntity(2, 'bbbbbbb', 2, 'b-b-b-b-b'),
SessionsEntity(3, 'ccccccc', 2, 'c-c-c-c-c'),
];
List<DropdownMenuItem<SessionsEntity>> items = List();
for (SessionsEntity session in sessions) {
items.add(
DropdownMenuItem(
value: session,
child: Text(session.sessionName),
),
);
}
return items;
}
onChangeDropdownItem(SessionsEntity selectedSession) {
setState(() {
sessionData = selectedSession;
});
}
}
class SessionsEntity {
final int id;
String sessionName;
int sessionType;
String dateTime;
SessionsEntity(this.id, this.sessionName,this.sessionType, this.dateTime);
}
The problem lies at the below line.
sessionData = _dropdownMenuItems[0].value;
Since your are initializing sessionData variable every time in the build() method, the call to setState() has no effect.
Basically setState() calls the build() method again with the new data but when you initialize sessionData insiside build() it gets overwritten with same value every time build() is called.
You should declare the use the _dropdownMenuItems list as a class variable instead of inside build() and use the initState() method to initialize the _dropdownMenuItems and sessionData variables only once. Here is an example -
class _MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState()=>_MyAppState();
}
class _MyAppState extends State<_MyApp> {
//Remove below line from build method
List<DropdownMenuItem<SessionsEntity>> _dropdownMenuItems;
SessionsEntity sessionData;
#override
void initState() {
super.initState();
_dropdownMenuItems = buildDropdownMenuItems();
sessionData = _dropdownMenuItems[0].value;
}
#override
Widget build(BuildContext context) {
//Rest of the code remains same
}
}
Following will help you. The sample widget is made for selecting a reason.
Sample widget code :
Widget dropdownReasons() {
return new DropdownButton<String>(
value: _currentReason,
items: _dropDownMenuItems,
onChanged: changedDropDownItem);
}
}
The function below handles what to do when the drop down value is changed :
void changedDropDownItem(String selectedReason) {
setState(() => _currentReason = selectedReason; // <-- This is the most important line
}
Try below code, this will work fine
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: new MyApp(),),
);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
SessionsEntity sessionData;
List<DropdownMenuItem<SessionsEntity>> _dropdownMenuItems;
// Build method is called whenever there is change in the state or ui, so instead of initializing the dropdown menu items and initial selected value of dropdown menu in build method you should initialize it in initState method which is called only once.
#override
void initState() {
// TODO: implement initState
_dropdownMenuItems = buildDropdownMenuItems();
sessionData = _dropdownMenuItems[0].value;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: DropdownButtonHideUnderline(
child: Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.white,
),
child: Container(
child: Center(
child: DropdownButton(
items: _dropdownMenuItems,
isDense: true,
value: sessionData,
onChanged: onChangeDropdownItem,
isExpanded: true,
hint: Text('please select item'),
),
),
),
),
),
);
}
List<DropdownMenuItem<SessionsEntity>> buildDropdownMenuItems() {
List<SessionsEntity> sessions = [
SessionsEntity(1, 'aaaaaaa', 1, 'a-a-a-a-a'),
SessionsEntity(2, 'bbbbbbb', 2, 'b-b-b-b-b'),
SessionsEntity(3, 'ccccccc', 2, 'c-c-c-c-c'),
];
List<DropdownMenuItem<SessionsEntity>> items = List();
for (SessionsEntity session in sessions) {
items.add(
DropdownMenuItem(
value: session,
child: Text(session.sessionName),
),
);
}
return items;
}
onChangeDropdownItem(SessionsEntity selectedSession) {
setState(() {
sessionData = selectedSession;
});
}
}
class SessionsEntity {
final int id;
String sessionName;
int sessionType;
String dateTime;
SessionsEntity(this.id, this.sessionName,this.sessionType, this.dateTime);
}
I have a ListView builder that creates a few ListTitle's with a checkbox inside them.
when I setState on the onChanged on a checkbox, the value doesn't seem to change.
class ProjectPage extends StatefulWidget {
final project;
ProjectPage({Key key, this.project}) : super(key: key);
#override
_ProjectPageState createState() => new _ProjectPageState();
}
class _ProjectPageState extends State<ProjectPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
child: Column(
children: <Widget>[
new Expanded(
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) => new ItemsItem(item: widget.project.items[index]),
itemCount: widget.project.items.length,
),
),
],
),
),
);
}
}
class ItemsItem extends StatefulWidget {
final item;
ItemsItem({Key key, this.item}) : super(key: key);
#override
_ItemsItemState createState() => new _ItemsItemState();
}
class _ItemsItemState extends State<ItemsItem> {
final GlobalKey<ScaffoldState> _mainState = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
bool _isCompleted = widget.item.isCompleted;
return new ListTile(
key: _mainState,
title: new Row(
children: <Widget>[
new Expanded(child: new Text(widget.item.name)),
new Checkbox(
value: _isCompleted,
onChanged: (bool newValue) {
setState(() {
_isCompleted = newValue;
});
},
),
],
),
);
}
}
this doesn't seem to change the value
setState(() {
_isCompleted = newValue;
});
any ideas?
edit: Item class
class Item {
final int id;
final String name;
final bool isCompleted;
Item({
this.id,
this.name,
this.isCompleted,
});
Item.fromJson(Map json)
: id = json['id'],
name = json['name'],
isCompleted = json['isCompleted'],
set isCompleted(bool value) {
isCompleted = value;
}
}
_isCompleted is a local variable inside the build method. When the Checkbox's state changes you set the local variable to the new value. setState results in the build method being called again which fetches the old and unchanged value from widget.item.isCompleted. You need to set widget.item.isCompleted to the new changed value:
setState(() {
widget.item.isCompleted = newValue;
});
Btw since your ItemsItem is just a ListTile containing a row with a Text and a Checkbox you should rather use the built-in widget CheckboxListTile