Set value of Dropdown Button manually - flutter

I have two widgets which are siblings in a container. One widget is a custom DropdownButton, the other one is a custom IconButton:
Parent widget:
static int _currentValue = 0;
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: GCWDropDownButton(
onChanged: (value) {
setState(() {
_currentValue = value;
});
}
),
),
GCWIconButton(
iconData: Icons.add,
onPressed: () {
print(_currentValue);
setState(() {
_currentValue++;
// <------------- how to set value to Dropdown Button
});
},
),
],
);
}
Dropdown widget:
class GCWDropDownButton extends StatefulWidget {
final Function onChanged;
const GCWDropDownButton({Key key, this.onChanged}) : super(key: key);
#override
_GCWDropDownButtonState createState() => _GCWDropDownButtonState();
}
class _GCWDropDownButtonState extends State<GCWDropDownButton> {
int _dropdownValue = 1;
#override
Widget build(BuildContext context) {
return Container(
child: DropdownButton(
value:_dropdownValue,
icon: Icon(Icons.arrow_downward),
onChanged: (newValue) {
setState(() {
_dropdownValue = newValue;
widget.onChanged(newValue);
});
},
items: ...
),
);
}
}
I want to change the DropdownButton's value to be increased after pressing the IconButton. If it were a TextField I'd use a Controller.
But how can I achieve this with the Dropdown?

You're trying to store the same value in 2 different states: in a parent and in a child one. In your case, it's better to do that in parent's state and to pass current value to the child.
int _currentIndex;
#override
Widget build(BuildContext context) {
...
child: Row(
children: <Widget>[
Expanded(
child: GCWDropDownButton(
currentIndex: _currentIndex,
onChanged: (index) {
setState(() {
_currentIndex = index;
});
},
),
),
GCWIconButton(
iconData: Icons.add,
onPressed: () {
setState(() {
if (_currentIndex == null) {
_currentIndex = 0;
} else {
_currentIndex++;
}
});
},
),
],
)
...
class GCWDropDownButton extends StatefulWidget {
final Function onChanged;
final int currentIndex;
const GCWDropDownButton({Key key, this.onChanged, this.currentIndex}) : super(key: key);
#override
_GCWDropDownButtonState createState() => _GCWDropDownButtonState();
}
class _GCWDropDownButtonState extends State<GCWDropDownButton> {
#override
Widget build(BuildContext context) {
final values = ['one', 'two', 'three'];
final currentValue = widget.currentIndex == null
? null
: values[min(values.length - 1, widget.currentIndex)]; // Not going out of range
return Container(
child: DropdownButton(
value: currentValue,
icon: Icon(Icons.arrow_downward),
onChanged: (newValue) {
setState(() {
widget.onChanged(values.indexOf(newValue));
});
},
items: values.map((v) =>
DropdownMenuItem(
child: Text(v.toString()),
value: v,
key: Key(v.toString())
)
).toList()
),
);
}
}
Or it would be even better to place DropdownButton and GCWIconButton in one stateful widget, so both widgets share the same state:
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: GCWDropDownButton()
),
);
}
}
class GCWDropDownButton extends StatefulWidget {
#override
_GCWDropDownButtonState createState() => _GCWDropDownButtonState();
}
class _GCWDropDownButtonState extends State<GCWDropDownButton> {
int _currentIndex;
final values = ['one', 'two', 'three'];
#override
Widget build(BuildContext context) {
final currentValue = _currentIndex == null ? null : values[_currentIndex];
return Row(
children: <Widget>[
Expanded(
child:Container(
child: DropdownButton(
value: currentValue,
icon: Icon(Icons.arrow_downward),
onChanged: (newValue) {
setState(() {
_currentIndex = values.indexOf(newValue);
});
},
items: values.map((v) =>
DropdownMenuItem(
child: Text(v.toString()),
value: v,
key: Key(v.toString())
)
).toList()
),
),
),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
if (_currentIndex == null) {
_currentIndex = 0;
} else
// Not going out of range
if (_currentIndex != values.length - 1) {
_currentIndex++;
}
});
},
),
],
);
}
}

Related

Flutter setState not updating variable inside ExpansionPanelList

I'm trying to create a filter that update the selected option like the image using a ExpansionPanelList, something like this...
Goal
In my code I'm trying to update a subtitle Text from a property returned from the body of the same ListTile Widget which contain the RadioListTile Widget inside of ExpansionPanel Widget inside of ExpansionPanelList Widget.
The value I want is from another StatefulWidget class where the RadioListTile works perfectly, and the value is returned by a Callback to the class I need to use this variable named _orderByOptionSelected, but the variable I'm using is not updated even inside of the setState method.
Here is the class that contains the RadioListTile selection:
class ElementFilterOrderBy extends StatefulWidget {
const ElementFilterOrderBy({Key? key, required this.onChanged})
: super(key: key);
static const String best = 'best';
static const String reviews = 'reviews';
static const String price = 'price';
static const String location = 'location';
final Function(String) onChanged;
#override
State<ElementFilterOrderBy> createState() => _ElementFilterOrderByState();
}
class _ElementFilterOrderByState extends State<ElementFilterOrderBy> {
String _orderBySelection = ElementFilterOrderBy.best;
#override
Widget build(BuildContext context) {
return Column(
children: [
RadioListTile<String>(
title: const Text(ElementFilterOrderBy.best),
value: ElementFilterOrderBy.best,
groupValue: _orderBySelection,
onChanged: (value) {
setState(() {
_orderBySelection = value!;
widget.onChanged(_orderBySelection);
});
},
activeColor: kPrimaryColor,
),
RadioListTile<String>(
title: const Text(ElementFilterOrderBy.reviews),
value: ElementFilterOrderBy.reviews,
groupValue: _orderBySelection,
onChanged: (value) {
setState(() {
_orderBySelection = value!;
widget.onChanged(_orderBySelection);
});
},
activeColor: kPrimaryColor,
),
RadioListTile<String>(
title: const Text(ElementFilterOrderBy.price),
value: ElementFilterOrderBy.price,
groupValue: _orderBySelection,
onChanged: (value) {
setState(() {
_orderBySelection = value!;
widget.onChanged(_orderBySelection);
});
},
activeColor: kPrimaryColor,
),
RadioListTile<String>(
title: const Text(ElementFilterOrderBy.location),
value: ElementFilterOrderBy.location,
groupValue: _orderBySelection,
onChanged: (value) {
setState(() {
_orderBySelection = value!;
widget.onChanged(_orderBySelection);
});
},
activeColor: kPrimaryColor,
),
],
);
}
}
And this is my class where I'm trying to update the value returned:
class CustomBottomSheet extends StatefulWidget {
const CustomBottomSheet({Key? key}) : super(key: key);
#override
State<CustomBottomSheet> createState() => _CustomBottomSheetState();
}
class _CustomBottomSheetState extends State<CustomBottomSheet> {
late String _orderByOptionSelected;
late String _searchLocation;
late List<ItemExpansionPanel> _optionsFilter;
#override
void initState() {
super.initState();
_orderByOptionSelected = 'best';
_searchLocation = 'Actual Location';
_optionsFilter = [
ItemExpansionPanel(
headerValue: kFilterOptionOrderBy,
widgetBody: ElementFilterOrderBy(
onChanged: (selectedOption) {
setState(() {
_orderByOptionSelected = selectedOption;
});
},
),
optionSelected: _orderByOptionSelected,
),
ItemExpansionPanel(
headerValue: kFilterOptionLocation,
widgetBody: Container(),
optionSelected: _searchLocation,
),
];
}
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(kPaddingApp),
child: Column(
children: [
const Text(
kFilterTitle,
style: kTextStyleBoldBlackBig,
),
const SizedBox(
height: kMarginApp,
),
Expanded(
child: SingleChildScrollView(
child: _buildPanel(),
),
),
],
),
);
}
Widget _buildPanel() {
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_optionsFilter[index].isExpanded = !isExpanded;
});
},
children: _optionsFilter.map<ExpansionPanel>((ItemExpansionPanel item) {
return ExpansionPanel(
canTapOnHeader: true,
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
subtitle: Text(
item.optionSelected,
style: const TextStyle(
color: kAccentColor,
),
),
);
},
body: item.widgetBody,
isExpanded: item.isExpanded,
);
}).toList(),
);
}
}
// stores ExpansionPanel state information
class ItemExpansionPanel {
ItemExpansionPanel({
required this.headerValue,
required this.widgetBody,
required this.optionSelected,
this.isExpanded = false,
});
final Widget widgetBody;
final String headerValue;
bool isExpanded;
String optionSelected;
}
Edit 1: Added more elements on the list to only change the ItemExpansionPanel selected
You should use _orderByOptionSelected as text value not item.optionSelected.
go to CustomBottomSheet then _buildPanel() widget then change it to this.
Widget _buildPanel() {
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_optionsFilter[index].isExpanded = !isExpanded;
});
},
children: _optionsFilter.map<ExpansionPanel>((ItemExpansionPanel item) {
return ExpansionPanel(
canTapOnHeader: true,
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
subtitle: Text(
_orderByOptionSelected,
// item.optionSelected, <== DELETE This
style: const TextStyle(
color: Colors.purple,
),
),
);
},
body: item.widgetBody,
isExpanded: item.isExpanded,
);
}).toList(),
);
}
}

Cannot change Dropdown button value Flutter

I want to get the initial value of the Dropdown button from firebase;
but when I try to set the governorateDDValue = selectedUser.governorate; inside the build method
the value of Dropdown get the value from firebase but I cannot change it
DropdownButton.gif
this my code
class UserInfo extends StatefulWidget {
static const routeName = '/UserInfoScreen';
const UserInfo({Key? key}) : super(key: key);
#override
_UserInfoState createState() => _UserInfoState();
}
class _UserInfoState extends State<UserInfo> {
late User selectedUser;
final date = DateFormat('yyyy-MM-dd').format(DateTime.now()).toString();
var governorateDDValue;
#override
Widget build(BuildContext context) {
final userList= Provider.of<List<User>>(context);
final userID = ModalRoute.of(context)!.settings.arguments as String;
selectedUser =
userList.firstWhere((user) => user.id == userID);
// this line makes dropdown value always equal to value from firestore
governorateDDValue = selectedUser.governorate;
return Scaffold(
appBar: AppBar(
title: Text('report'),
),
body: SingleChildScrollView(
child: Container(
child: Row(
children: <Widget>[
Text('Governorate',),
Container(height: 5),
DropdownButton<String>(
value: governorateDDValue,
icon: const Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
onChanged: (String? newValue) {
setState(() {
governorateDDValue = newValue!;
});
},
items: Constants.governoratesOfEgypt
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
],
),
),
),
);
}
}
thanks in advance
Because you use governorateDDValue = selectedUser.governorate; inside build widget so the Dropdown menu will reset its value every time you change it
the build widget will rebuild and the value of the dropdown will stay equal to the value from firebase
you should use governorateDDValue = selectedUser.governorate; outside the build widget
this code should work will
class UserInfo extends StatefulWidget {
static const routeName = '/UserInfoScreen';
const UserInfo({Key? key}) : super(key: key);
#override
_UserInfoState createState() => _UserInfoState();
}
class _UserInfoState extends State<UserInfo> {
var loading = false;
late User selectedUser;
final date = DateFormat('yyyy-MM-dd').format(DateTime.now()).toString();
var governorateDDValue;
#override
void initState() {
super.initState();
loading = true;
print('Future.delayed outside');
print(loading);
Future.delayed(Duration.zero, () {
governorateDDValue = selectedUser.governorate;
setState(() {
loading = false;
});
});
}
#override
Widget build(BuildContext context) {
final userList = Provider.of<List<User>>(context);
final userID = ModalRoute
.of(context)!
.settings
.arguments as String;
selectedUser =
userList.firstWhere((user) => user.id == userID);
// this line makes dropdown value always equal to value from firestore
governorateDDValue = selectedUser.governorate;
return Scaffold(
appBar: AppBar(
title: Text('report'),
),
body: SingleChildScrollView(
child: Container(
child: Row(
children: <Widget>[
Text('Governorate',),
Container(height: 5),
DropdownButton<String>(
value: governorateDDValue,
icon: const Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
onChanged: (String? newValue) {
setState(() {
governorateDDValue = newValue!;
});
},
items: Constants.governoratesOfEgypt
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
],
),
),
),
);
The reason why you cannot change it is because every time setState is called your build method is called. Therefore your value will always be set to governorateDDValue = selectedUser.governorate; So to allow changes you should place this governorateDDValue = selectedUser.governorate; in iniState
Or what you can do is like this so that it will only set it once
class UserInfo extends StatefulWidget {
static const routeName = '/UserInfoScreen';
const UserInfo({Key? key}) : super(key: key);
#override
_UserInfoState createState() => _UserInfoState();
}
class _UserInfoState extends State<UserInfo> {
late User selectedUser;
final date = DateFormat('yyyy-MM-dd').format(DateTime.now()).toString();
bool initState = true; // ADD HERE
var governorateDDValue;
#override
Widget build(BuildContext context) {
final userList= Provider.of<List<User>>(context);
final userID = ModalRoute.of(context)!.settings.arguments as String;
selectedUser =
userList.firstWhere((user) => user.id == userID);
// this line makes dropdown value always equal to value from firestore
if(initState){ // ADD HERE
governorateDDValue = selectedUser.governorate;
initState = false; // ADD HERE
}
return Scaffold(
appBar: AppBar(
title: Text('report'),
),
body: SingleChildScrollView(
child: Container(
child: Row(
children: <Widget>[
Text('Governorate',),
Container(height: 5),
DropdownButton<String>(
value: governorateDDValue,
icon: const Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
onChanged: (String? newValue) {
setState(() {
governorateDDValue = newValue!;
});
},
items: Constants.governoratesOfEgypt
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
],
),
),
),
);
}

How can I change DropdownButton values from another widget in Flutter?

I am using that DropdownButton inside of the Stateless wigdet but I want to change that DropdownButton values from another Stateful widget. Likewise with using DropdownButton value, I want to change another stateless widget's container color.
Here is my First Stateless widget
List<String> dropdownValues = ['red', 'green', 'blue'];
#override
Widget build(BuildContext context) {
return Container(
child: DropdownButton(
items: dropdownValues
.map((value) => DropdownMenuItem(
child: Text(value),
value: value,
))
.toList(),
onChanged: (String newValue) {},
isExpanded: false,
hint: Text('Chose Color'),
selectedItemBuilder: ,
),
);
}
}
This is my Stateful widget
bool isLightOn = false;
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
padding: new EdgeInsets.all(5.0),
child: Column(
children: <Widget>[
LightBulb(
isLightOn: isLightOn,
),
LightButton(
isLightOn: isLightOn,
onButtonPress: onButtonPress,
),
LightColorSelector(),
],
),
);
}
void onButtonPress() {
if (isLightOn == false) {
setState(() {
isLightOn = true;
});
} else {
setState(() {
isLightOn = false;
});
}
}
}
How can I handle these problems and how can I manipulate DropdownButton values?
Likewise, I want to reflect that DropdownButton value with changing LightBulb's container color.
Here is LightBulb class
final bool isLightOn;
LightBulb({this.isLightOn});
#override
Widget build(BuildContext context) {
return Container(
color: isLightOn == false ? Colors.red : Colors.green,
padding: EdgeInsets.all(5.0),
child: isLightOn == false ? Text("OFF") : Text("ON"),
);
}
}
Here is a full working example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> dropdownValues = ['red', 'green', 'blue'];
String selected;
Color color;
#override
Widget build(BuildContext context) {
return Material(
child: Column(children: <Widget>[
DropdownButton<String>(
items: dropdownValues
.map((value) => DropdownMenuItem(
child: Text(value),
value: value,
))
.toList(),
onChanged: (String newValue) {
setState(() {
selected = newValue;
if (newValue == "red") color = Colors.red;
if (newValue == "green") color = Colors.green;
if (newValue == "blue") color = Colors.blue;
});
},
//isExpanded: false,
hint: Text('Chose Color'),
//selectedItemBuilder: ,
),
Container(
color: color != null ? color : Colors.black,
padding: EdgeInsets.all(5.0),
child: selected != null ? Text(selected) : Text("OFF", style: TextStyle(color: Colors.white)),
)
]),
);
}
}

Flutter: set DropdownButtonFormField selection programatically

Is there a way to set the value of a DropdownButtonFormField programatically?
I tried manipulating the value property, but it does no show any effect.
Example:
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
TestPage({Key key}) : super(key: key);
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
int _selectedId;
List<Item> _items = [];
#override
void initState() {
super.initState();
for (int i = 0; i < 5; i++) {
_items.add(Item(i, "choice " + i.toString()));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test"),
),
body: Column(
children: [
DropdownButtonFormField(
isExpanded: true,
items: _items.map((item) {
return new DropdownMenuItem(
value: item.id,
child: Text(item.text),
);
}).toList(),
onChanged: (value) {
setState(() => _selectedId = value);
},
value: _selectedId,
decoration: InputDecoration(
labelText: "select me",
),
),
RaisedButton(
child: Text('set selected'),
onPressed: () {
setState(() {
_selectedId = 3;
});
},
),
RaisedButton(
child: Text('get selected'),
onPressed: () {
print(_selectedId.toString());
},
)
],
),
);
}
}
class Item {
int id;
String text;
Item(this.id, this.text);
}
When setting _selectedId to 3 via the button, nothing happens, the dropdown does not update. If I set _selectedId to 3 on variable initialization, the third choice is selected when the page loads.
However I need it programatically.

How to onClick listener on DropdownMenuItem

I have build the code of DropdownMenuItem, now when i click an item from dropdownmenuitem it should move to another screen.Below is the code
class TimesScreen extends StatefulWidget {
#override
_TimesScreenState createState() => _TimesScreenState();
}
class _TimesScreenState extends State<TimesScreen> {
var gender;
#override
Widget build(BuildContext context) {
DropdownButton(
hint: Text("Select",
style: TextStyle(color: Colors.white),),
onChanged: (val){
setState(() {
this.gender=val;
});
},
value: this.gender,
items: [
DropdownMenuItem(
//onTap:
value: 'Earth',
child: Text('Earth'
),
),
DropdownMenuItem(
//onTap:
value: 'Mars',
child: Text('Mars'
),
),)]
You can wrap your Text widget with GestureDetector to which has an onTap function which you can use to execute your desired code. For more details look at this: https://api.flutter.dev/flutter/widgets/GestureDetector-class.html
This should work:
DropdownMenuItem(
value: 'Earth',
child: GestureDetector(
onTap: () {
// navigate code...
},
child: Text('Earth')
),
),
After applying fayeed's solution, I noticed that this only makes the text inside the dropdown clickable. To fix this, you can simply use DropdownButton.onChanged.
Full widget:
class TimesScreen extends StatefulWidget {
#override
_TimesScreenState createState() => _TimesScreenState();
}
class _TimesScreenState extends State<TimesScreen> {
var gender;
#override
Widget build(BuildContext context) {
return DropdownButton(
hint: Text("Select"),
value: this.gender,
items: [
DropdownMenuItem(value: 'Earth', child: Text('Earth')),
DropdownMenuItem(value: 'Mars', child: Text('Mars')),
],
onChanged: (val) {
setState(() {
this.gender = val;
});
switch (val) {
case 'Earth':
Navigator.pushNamed(context, '/earth_target_page');
break;
case 'Mars':
Navigator.pushNamed(context, '/mars_target_page');
break;
}
},
);
}
}