How to onClick listener on DropdownMenuItem - flutter

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

Related

Flutter DropDownButton hide button (only the button) when menu is open

It is possible to hide the DropDownButton (only the button) when menu is open?
I need the background of the menu to be transparent, when I do it I find that the button and the menu are misaligned and also that you can see the text of the button in the background (see image)
this is my problem
I need that once the menu is opened it looks more or less like the following image
this is a fake recreation of the menu i need
If somebody know any answer that is an alternative to using the DropDownButton, made with containers, some packages or whatever, and that can do the same functions, that would be welcome too.
Actual Code
class DropDownMenuAppBar extends StatefulWidget {
const DropDownMenuAppBar({
Key? key,
}) : super(key: key);
#override
State<DropDownMenuAppBar> createState() => _DropDownMenuAppBarState();
}
class _DropDownMenuAppBarState extends State<DropDownMenuAppBar> {
String dropdownValue = 'Today';
#override
Widget build(BuildContext context) {
return DropdownButtonHideUnderline(
child: DropdownButton<String>(
alignment: Alignment.bottomLeft,
dropdownColor: Colors.white10,
value: dropdownValue,
icon: const Icon(
Icons.arrow_drop_down,
color: Colors.white,
),
elevation: 0,
style: AppTextStyle.boldLarge.copyWith(color: Colors.white),
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
},
items: <String>['Today', 'All Habits']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
);
}
}
Thanks
You can add a focus to the dropdown to know if its open or closed and set the visibility of the selected item like
class DropDownMenuAppBar extends StatefulWidget {
const DropDownMenuAppBar({Key? key}) : super(key: key);
#override
State<DropDownMenuAppBar> createState() => _DropDownMenuAppBarState();
}
class _DropDownMenuAppBarState extends State<DropDownMenuAppBar> {
String dropdownValue = 'Today';
late FocusNode _focus;
#override
void initState() {
super.initState();
_focus = FocusNode();
_focus.addListener(_handleFocusChange);
}
void _handleFocusChange() {
if (_focus.hasFocus != _focused) {
setState(() {
_focused = _focus.hasFocus;
});
}
print(!_focused);
}
bool _focused = true;
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
alignment: Alignment.bottomLeft,
dropdownColor: Colors.white10,
value: dropdownValue,
focusNode: _focus,
icon: Visibility(
visible: _focused,
child: Icon(
Icons.arrow_drop_down,
color: Colors.white,
),
),
elevation: 0,
style: const TextStyle(color: Colors.deepPurple),
selectedItemBuilder: (BuildContext context) {
return <String>['Today', 'All Habits']
.map((String value) {
return Visibility(
visible: _focused,
child: DropdownMenuItem<String>(
value: value,
child: Text(value),
),
);
}).toList();
},
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
if (_focus.hasFocus) {
_focus.unfocus();
} else {
_focus.requestFocus();
}
},
items: <String>['Today', 'All Habits']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}

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 rebuild parent widget

I need help. I have a Dropdown widget in LanguageDropdown class, where the user can select the language. And the widget is inside a settings page widget in Settings class. The language changes on other pages, but not on current one. How can I rebuild that specific page, so the language changes on this one also?
See the code below
import 'package:jptapp/features/settings/change_language/app_localization.dart';
class LanguageDropDown extends StatefulWidget {
#override
_LanguageDropDownState createState() {
return _LanguageDropDownState();
}
}
class _LanguageDropDownState extends State<LanguageDropDown> {
String _value = allTranslations.currentLanguage;
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
items: [
DropdownMenuItem<String>(
child: Text('English'),
value: 'en',
),
DropdownMenuItem<String>(
child: Text('Magyar'),
value: 'hu',
),
DropdownMenuItem<String>(
child: Text('Srpski'),
value: 'rs',
),
],
onChanged: (String value) {
setState(() async{
_value = value;
await allTranslations.setNewLanguage(_value);
});
},
hint: Text(_value),
value: _value,
);
}
}
import 'package:jptapp/core/constants/colors.dart';
import 'package:jptapp/features/settings/change_language/app_localization.dart';
import 'package:jptapp/features/settings/widgets/widgets.dart';
class Settings extends StatefulWidget {
#override
_SettingsState createState() => _SettingsState();
}
class _SettingsState extends State<Settings> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
backgroundColor: MyColors.appBarColor,
title: Text(
allTranslations.text('settings'),
),
),
body: ListView(
children: ListTile.divideTiles(
context: context,
tiles: [
ListTile(
trailing: ThemeChangerAnimationButton(),
title: Text(
allTranslations.text('darkmode'),
),
),
ListTile(
trailing: LanguageDropDown(),
title: Text(
allTranslations.text('language'),
),
),
],
).toList(),
),
);
}
}
I'm not sure this will work but try this:
import 'package:flutter/material.dart';
import 'package:jptapp/features/settings/change_language/app_localization.dart';
class LanguageDropDown extends StatefulWidget {
#override
_LanguageDropDownState createState() {
return _LanguageDropDownState();
}
}
class _LanguageDropDownState extends State<LanguageDropDown> {
String _value = allTranslations.currentLanguage;
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
items: [
DropdownMenuItem<String>(
child: Text('English'),
value: 'en',
),
DropdownMenuItem<String>(
child: Text('Magyar'),
value: 'hu',
),
DropdownMenuItem<String>(
child: Text('Srpski'),
value: 'rs',
),
],
onChanged: (String value) {
setState(() async {
_value = value;
await allTranslations.setNewLanguage(_value);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Settings()
));
});
},
hint: Text(_value),
value: _value,
);
}
}

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