Cannot change Dropdown button value Flutter - 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(),
),
],
),
),
),
);
}

Related

How to show selected checkbox on prev screen?

I need to display checkboxes selected by the user on the previous page using pop()
I have a function that displays the user's message on the previous page and I need to pass the selected checkboxes in the same way. How to pass them as arguments to pop()?
Screen with checkboxes:
const TextScreen({Key? key}) : super(key: key);
#override
State<TextScreen> createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
// initial values for checkboxes
bool _privacy = false;
bool _termsOfUse = false;
// text controller for message input
TextEditingController textController = TextEditingController();
#override
void dispose() {
textController.dispose();
super.dispose();
}
// go to result screen
void getResult(BuildContext context) {
String valueResult = textController.text;
Navigator.pop(context, valueResult);
}
#override
Widget build(BuildContext context) {
//change state for privacy checkbox
_onPrivacyChange(value) {
setState(() {
_privacy = value!;
});
}
//change state for terms of use checkbox
_onTermsOfUSeChange(value) {
setState(() {
_termsOfUse = value!;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
decoration: const InputDecoration(labelText: 'Message')),
const SizedBox(height: 20),
CheckboxListTile(
title: const Text('Privacy'),
controlAffinity: ListTileControlAffinity.leading,
value: _privacy,
onChanged: _onPrivacyChange,
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
title: const Text('Terms of use'),
controlAffinity: ListTileControlAffinity.leading,
value: _termsOfUse,
onChanged: _onTermsOfUSeChange,
contentPadding: EdgeInsets.zero,
),
ElevatedButton(
onPressed: () {
getResult(context);
},
child: const Text('Display result'))
],
)),
);
}
}
Screen with results display:
class ResultScreen extends StatefulWidget {
const ResultScreen({Key? key}) : super(key: key);
#override
State<ResultScreen> createState() => _ResultScreenState();
}
class _ResultScreenState extends State<ResultScreen> {
String? _valueText = '';
#override
Widget build(BuildContext context) {
// navigation to next screen
void _navToNextScreen(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
// update widget after result comes back
setState(() {
_valueText = result;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Results'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_navToNextScreen(context);
},
child: const Text('Enter data'),
),
const SizedBox(height: 50),
Text('Message: $_valueText'),
const SizedBox(height: 20),
Text('Checkboxes: '),
],
)),
);
}
}
I think this should be the job of a simple state management strategy; for communication between separate widgets (in this case, two page widgets), that's the cleanest approach. You should create a common service to which both page widgets are subscribed: one to trigger the changes, the other to capture them and display them, using a ChangeNotifier service along with Consumer widgets, as shown below:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => SelectedData(),
child: MyApp()
)
);
}
class SelectedData extends ChangeNotifier {
bool _privacy = false;
bool _termsOfUse = false;
String _valueResult = '';
bool get privacy => _privacy;
bool get termsOfUse => _termsOfUse;
String get valueResult => _valueResult;
set privacy(bool value) {
_privacy = value;
notifyListeners();
}
set termsOfUse(bool value) {
_termsOfUse = value;
notifyListeners();
}
set valueResult(String value) {
_valueResult = value;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ResultScreen(),
),
),
);
}
}
class TextScreen extends StatefulWidget {
const TextScreen({Key? key}) : super(key: key);
#override
State<TextScreen> createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
// text controller for message input
TextEditingController textController = TextEditingController();
#override
void initState() {
super.initState();
}
#override
void dispose() {
textController.dispose();
super.dispose();
}
// go to result screen
void getResult(BuildContext context) {
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
SelectedData data = Provider.of<SelectedData>(context, listen: false);
textController.text = data.valueResult;
//change state for privacy checkbox
_onPrivacyChange(value) {
data.privacy = value;
}
//change state for terms of use checkbox
_onTermsOfUSeChange(value) {
data.termsOfUse = value;
}
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Consumer<SelectedData>(
builder: (context, selectedData, child) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
onChanged: (value) {
data.valueResult = value;
},
decoration: const InputDecoration(labelText: 'Message')),
const SizedBox(height: 20),
CheckboxListTile(
title: const Text('Privacy'),
controlAffinity: ListTileControlAffinity.leading,
value: selectedData.privacy,
onChanged: _onPrivacyChange,
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
title: const Text('Terms of use'),
controlAffinity: ListTileControlAffinity.leading,
value: selectedData.termsOfUse,
onChanged: _onTermsOfUSeChange,
contentPadding: EdgeInsets.zero,
),
ElevatedButton(
onPressed: () {
getResult(context);
},
child: const Text('Display result'))
],
));
}
),
);
}
}
class ResultScreen extends StatefulWidget {
const ResultScreen({Key? key}) : super(key: key);
#override
State<ResultScreen> createState() => _ResultScreenState();
}
class _ResultScreenState extends State<ResultScreen> {
#override
Widget build(BuildContext context) {
// navigation to next screen
void _navToNextScreen(BuildContext context) async {
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Results'),
),
body: Consumer<SelectedData>(
builder: (context, selectedData, child) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_navToNextScreen(context);
},
child: const Text('Enter data'),
),
const SizedBox(height: 50),
Text('Message: ${selectedData.valueResult}'),
const SizedBox(height: 20),
const Text('Checkboxes: '),
Text('Privacy: ${selectedData.privacy}'),
Text('Terms of Use: ${selectedData.termsOfUse}')
],
));
}
),
);
}
}
Here's the output when you implement it this way:
So from what i see is you are only passing one value that is message and you what many values to pass at a time so here the map can be used and as pop() function takes dynamic returns you can pass any thing.
From your example i have created a sample example that will be a working proof which will demostrate the using map for passing data to previous screen.
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(
debugShowCheckedModeBanner: false,
home: ResultScreen(),
);
}
}
class TextScreen extends StatefulWidget {
const TextScreen({Key? key}) : super(key: key);
#override
_TextScreenState createState() => _TextScreenState();
}
class _TextScreenState extends State<TextScreen> {
// initial values for checkboxes
bool _privacy = false;
bool _termsOfUse = false;
// text controller for message input
TextEditingController textController = TextEditingController();
#override
void dispose() {
textController.dispose();
super.dispose();
}
// go to result screen
void getResult(BuildContext context) {
String valueResult = textController.text;
final data = {
"message":valueResult,
"privacy": _privacy,
'terms':_termsOfUse,
};
Navigator.pop(context, data);
}
#override
Widget build(BuildContext context) {
//change state for privacy checkbox
_onPrivacyChange(value) {
setState(() {
_privacy = value!;
});
}
//change state for terms of use checkbox
_onTermsOfUSeChange(value) {
setState(() {
_termsOfUse = value!;
});
}
return Scaffold(
appBar: AppBar(
title: const Text('Enter data'),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: textController,
decoration: const InputDecoration(labelText: 'Message')),
const SizedBox(height: 20),
CheckboxListTile(
title: const Text('Privacy'),
controlAffinity: ListTileControlAffinity.leading,
value: _privacy,
onChanged: _onPrivacyChange,
contentPadding: EdgeInsets.zero,
),
CheckboxListTile(
title: const Text('Terms of use'),
controlAffinity: ListTileControlAffinity.leading,
value: _termsOfUse,
onChanged: _onTermsOfUSeChange,
contentPadding: EdgeInsets.zero,
),
ElevatedButton(
onPressed: () {
getResult(context);
},
child: const Text('Display result'))
],
)),
);
}
}
class ResultScreen extends StatefulWidget {
const ResultScreen({Key? key}) : super(key: key);
#override
State<ResultScreen> createState() => _ResultScreenState();
}
class _ResultScreenState extends State<ResultScreen> {
String? _valueText = '';
bool _privacyValue =false;
bool _termsOfUse = false;
#override
Widget build(BuildContext context) {
// navigation to next screen
void _navToNextScreen(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TextScreen()),
);
if(result !=null)
{
setState(() {
if(result['message']!=null )_valueText = result['message'];
if(result['privacy']!=null) _privacyValue = result['privacy'];
if(result['terms']!=null) _termsOfUse = result['terms'];
});
}
}
return Scaffold(
appBar: AppBar(
title: const Text('Results'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
_navToNextScreen(context);
},
child: const Text('Enter data'),
),
const SizedBox(height: 50),
Text('Message: $_valueText'),
const SizedBox(height: 20),
Text('Privacy Value: $_privacyValue '),
const SizedBox(height: 20),
Text('Terms Value: $_termsOfUse '),
],
)),
);
}
}
You can make changes as per your needs, So let me know if it works.

is there a way to get a value from a class to outside the class?

i have a drop down class which has in its build widget a dropdown widget which on changed it set state the value of selected option to a value in the class.
i used the class in my screen and I don't find a way to use the selected value :(
here is my code
class ServiceCategoryDropDownList extends StatefulWidget {
const ServiceCategoryDropDownList({ Key key }) : super(key: key);
#override
State<ServiceCategoryDropDownList> createState() => _ServiceCategoryDropDownListState();
}
class _ServiceCategoryDropDownListState extends State<ServiceCategoryDropDownList> {
String value;
#override
Widget build(BuildContext context) {
final servicecategories = Provider.of<List<ServiceCategory>> (context);
return Container(
child:
DropdownButtonHideUnderline(
child:DropdownButton<String>(
value: value,
isExpanded: true,
icon: Icon(Icons.arrow_drop_down),
iconSize: 36,
items: servicecategories.map((item){
print(item.serviceCategoryName);
return DropdownMenuItem<String>(
value: item.serviceCategoryID,
child: Text(item.serviceCategoryName,style: TextStyle(color: Colors.black,fontSize: 14),),
);
}).toList(),
onChanged: (value) {
setState(() =>this.value = value);
print(value);
}
),
)
);
}
}
and here where i call for the class dropdown
class ServicesContent extends StatefulWidget {
const ServicesContent({ Key key }) : super(key: key);
#override
State<ServicesContent> createState() => _ServicesContentState();
}
class _ServicesContentState extends State<ServicesContent> {
#override
Widget build(BuildContext context) {
return
Scaffold(
body: StreamProvider<List<ServiceCategory>>.value(
value: Database().serviceCategory,
initialData: [],
child: Column(
children: [
ServiceCategoryDropDownList(),
ElevatedButton(
child: Text("Add"),
onPressed: () => print(ServiceCategoryDropDownList().value)
)
])));}
}
please any one help me :(
If I understood correctly your problem, do you need to get the selected value out of the dropdown widget? if it is yes, check this explanation and the code
Accept a callback property in your widget constructor
After that, call your callback passing the selected value
Now, you can read the selected value in your ServicesContent as the widget callback parameter
Dropdown widget
class ServiceCategoryDropDownList extends StatefulWidget {
// 1. Accept a callback property in you widget constructor
final Function onChanged;
const ServiceCategoryDropDownList({
Key? key,
required this.onChanged,
}) : super(key: key);
#override
State<ServiceCategoryDropDownList> createState() =>
_ServiceCategoryDropDownListState();
}
class _ServiceCategoryDropDownListState
extends State<ServiceCategoryDropDownList> {
String? value = '';
#override
Widget build(BuildContext context) {
final servicecategories = Provider.of<List<ServiceCategory>>(context);
return Container(
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: value,
isExpanded: true,
icon: const Icon(Icons.arrow_drop_down),
iconSize: 36,
items: servicecategories.map((item) {
print(item.serviceCategoryName);
return DropdownMenuItem<String>(
value: item.serviceCategoryID,
child: Text(
item.serviceCategoryName,
style: const TextStyle(
color: Colors.black,
fontSize: 14,
),
),
);
}).toList(),
onChanged: (value) {
setState(() => this.value = value);
// 2. Call your callback passing the selected value
widget.onChanged(value);
},
),
),
);
}
}
ServiceContent
class _ServicesContentState extends State<ServicesContent> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamProvider<List<ServiceCategory>>.value(
value: Database().serviceCategory,
initialData: [],
child: Column(
children: [
ServiceCategoryDropDownList(
onChanged: (String value) {
// 3. Now you can read the selected value here and make cool things
print(value);
},
),
ElevatedButton(
child: Text("Add"),
// 4. You can't do this ServiceCategoryDropDownList().value, so delete it and get the value at point 3
onPressed: () => print(ServiceCategoryDropDownList().value),
)
],
),
),
);
}
}
Let me know if you can solve your issue :)

Flutter: Get data back from StatefulWidget child class to parent

I'm new to flutter.
I have a page (Stateful Widget) in the app with a lot of widgets in a column. To improve the code readability, I took some widgets, and made them into seperate classes. For example, I made my dropdownmenu widget, into its only class, like this:
class DropDownMenuWidget extends StatefulWidget {
DropDownMenuWidget({Key? key}) : super(key: key);
#override
_DropDownMenuWidgetState createState() => _DropDownMenuWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _DropDownMenuWidgetState extends State<DropDownMenuWidget> {
String dropdownValue = 'One';
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: dropdownValue,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(
color: Colors.black,
fontSize: 20,
),
underline: Container(
height: 2,
color: Colors.blue,
),
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
},
items: MASLULIM
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}
Now, in the parent class, I display the widget like this:
DropDownMenuWidget(),
However, the problem is, when the user clicks on a item, I can only retrieve that value from the DropDownMenu class, and there the setState() method is called. However, I need to read this value in the parent class. How can I get it there?
Thanks
Instead of creating your dropdownValue variable in your Widget, you can get it from the parent Widget as following with the help of ValueNotifier
class DropDownMenuWidget extends StatefulWidget {
ValueNotifier dropdownValueNotifier;
DropDownMenuWidget(this.dropdownValueNotifier, {Key key}) : super(key: key);
#override
_DropDownMenuWidgetState createState() => _DropDownMenuWidgetState();
}
class _DropDownMenuWidgetState extends State<DropDownMenuWidget> {
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: widget.dropdownValueNotifier,
builder: (context, dropdownValue, _) {
return DropdownButton<String>(
value: dropdownValue,
// ...
onChanged: (String newValue) {
// simply change the value. You dont need setState anymore
widget.dropdownValueNotifier.value = newValue;
},
// ...
);
},
);
}
}
In the parent Widget, create the variable and pass it like this
ValueNotifier dropdownValueNotifier = ValueNotifier('One');
// ...
DropDownMenuWidget(dropdownValueNotifier),
In this case, you can use typedef
First in a separate DrobDown menu you can create the following icon outside of the class:
typedef OnItemSelectedDropDown = Function (String value);
Now you can apply this thing as follows :
class DropDownMenuWidget extends StatefulWidget {
final OnItemSelectedDropDown onItemSelected ;
DropDownMenuWidget({Key? key}) : super(key: key);
#override
_DropDownMenuWidgetState createState() => _DropDownMenuWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _DropDownMenuWidgetState extends State<DropDownMenuWidget> {
String dropdownValue = 'One';
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: dropdownValue,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(
color: Colors.black,
fontSize: 20,
),
underline: Container(
height: 2,
color: Colors.blue,
),
onChanged: (String value) {
//This line return Value
widget.onItemSelected.call(value);
},
items: MASLULIM
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}
When calling the class DropDownMenuWidget, it is called as follows on another screen:
String dropdownValue ;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('DropDown Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'New Value DropDown : $dropdownValue',
),
DropDownMenuWidget(
onItemSelected :(newValue){
setState(() {
dropdownValue = newValue ;
});
}
),
],
),
),
);
}

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.

Set value of Dropdown Button manually

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