How to get Value to update in Flutter DropdownButton from stream? - flutter

I'm trying to get a list from my firebase firestore and provide it as a dropdown button, but when the user selects the option it does not update on GUI.
I think the problems is where I instantiate the dropdownValue variable but I don't where else to place it.
class _LocationNameListState extends State<LocationNameList> {
#override
Widget build(BuildContext context) {
List dropdownOptions = <String>[];
String? dropdownValue;
return StreamBuilder(
stream: LocationController().getAllLocations(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("This is something wrong");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
for (var i = 0; i < snapshot.data!.docs.length; i++) {
dropdownOptions.add("${snapshot.data!.docs[i]['name']}");
}
print(dropdownOptions);
String dropdownValue = dropdownOptions[0];
return DropdownButton(
items: dropdownOptions
.map((e) => DropdownMenuItem(
value: e,
child: Text(e),
))
.toList(),
onChanged: (value) {
setState(() {
dropdownValue = value.toString();
print(dropdownValue);
});
},
value: dropdownValue,
);
},
);
}
}

The problem is that your dropDown value is set within your Build method:
Widget build(BuildContext context) {
List dropdownOptions = <String>[];
String? dropdown value;
return StreamBuilder(
...
So every setState it gets reset, since the build rebuilds.
To fix the error, move your value outside of the build method:
class _LocationNameListState extends State<LocationNameList> {
// --> Add this variable over here
List dropdownOptions = <String>[];
String? dropdownValue;
#override
Widget build(BuildContext context) {
...
}
I've managed to reproduce your problem with a simplified example. As you see dropdownValue will be reset, since it's within the build method:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyDropdown(),
),
),
);
}
}
class MyDropdown extends StatefulWidget {
const MyDropdown({Key? key}) : super(key: key);
#override
State<MyDropdown> createState() => _MyDropdownState();
}
class _MyDropdownState extends State<MyDropdown> {
#override
Widget build(BuildContext context) {
String dropdownValue = 'One';
return DropdownButton<String>(
value: dropdownValue,
icon: const Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
},
items: <String>['One', 'Two', 'Free', 'Four']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}
And to solve the issue:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyDropdown(),
),
),
);
}
}
class MyDropdown extends StatefulWidget {
const MyDropdown({Key? key}) : super(key: key);
#override
State<MyDropdown> createState() => _MyDropdownState();
}
class _MyDropdownState extends State<MyDropdown> {
// -->Simply set the value here
String dropdownValue = 'One';
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: dropdownValue,
icon: const Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
},
items: <String>['One', 'Two', 'Free', 'Four']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}

Every time setState is called, build method gets called. It means
String dropdownValue = dropdownOptions[0]; is called as well setting the value of variable to first item of the list.
You need to move dropdownValue to class level variable of your state class.
(String? dropdownValue = null)
Then replace above mentioned line with
if(dropdownValue == null) {
dropdownValue = dropdownOptions[0]
}

Related

How to change value on DropdownButton in onChange in Flutter

I am a beginner in the flutter I'm just learning flutter and I am stuck in this code how to solve this please help me?
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Application',
home: book(),
);
}
}
class book extends StatefulWidget{
#override
State<StatefulWidget> createState() {
return _bookstate();
}
}
class _bookstate extends State<book>{
String namebook = "";
var writter = ['A','B','C'];
var _currentItemSelected = 'A';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Stateful Widget'),
),
body: Container(
margin: EdgeInsets.all(20.0),
child: Column(
children:<Widget> [
TextField(
onChanged: (String userInput){
setState(() {
namebook=userInput;
});
},
),
DropdownButton<String>(
items: writter.map((String dropDownStringItem){
return DropdownMenuItem<String>(
value: dropDownStringItem,
child: Text(dropDownStringItem),
);
}).toList(),
onChanged: (String newValueSelected){
setState(() {
this._currentItemSelected = newValueSelected;
});
},
value: _currentItemSelected,
),
Text("Enter book name id $namebook",style: TextStyle(fontSize:20.0),),
],
),
),
);
}
}
and error show this message:
Error: The argument type 'void Function(String)' can't be assigned to the parameter type 'void Function(String?)?' because 'String?' is nullable and 'String' isn't.
You need to follow null safety rules, because your version supports null safety.
Simply change your code;
onChanged: (String? newValueSelected) {
setState(() {
this._currentItemSelected = newValueSelected!;
});
},
And I suggest check and learn what null safety is.
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Book(),
);
}
}
class Book extends StatefulWidget {
const Book({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _Bookstate();
}
}
class _Bookstate extends State<Book> {
String namebook = "";
var writter = ['A', 'B', 'C'];
var _currentItemSelected = 'A';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Stateful Widget'),
),
body: Container(
margin: const EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
TextField(
onChanged: (String userInput) {
setState(() {
namebook = userInput;
});
},
),
DropdownButton<String>(
items: writter.map((String dropDownStringItem) {
return DropdownMenuItem<String>(
value: dropDownStringItem,
child: Text(dropDownStringItem),
);
}).toList(),
onChanged: (String? newValueSelected) {
setState(() {
_currentItemSelected = newValueSelected!;
});
},
value: _currentItemSelected,
),
Text(
"Enter book name id $namebook",
style: const TextStyle(fontSize: 20.0),
),
],
),
),
);
}
}

How to open DropDownButton on init page

i want to make a function for init openDropDown(); when page loaded or tap on other widget
I found this in stackoverfow
but its not working maybe its outdated. how can i achieve. with flutter 2.10
here is my dropdown
DropdownButton(
isExpanded: true,
value: selectedValue,
items: [
for (var i = 0; i < this.clients.length; i++)
DropdownMenuItem(
child: Text(this.clients[i]['name'].toString() +
' (' +
this.clients[i]['wallet'].toString() +
') '),
value: this.clients[i]['id'].toString())
],
onChanged: (String? newValue) {
setState(() {
selectedValue = newValue!;
});
},
),
You can take advantage of GlobalKey to get this job done... Also use SchedulerBinding because in initState key would be null so you won't be able to open DropDown from null key... here is the proper code how you do that task
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter DropDownButton',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey _dropdownButtonKey = GlobalKey();
final Intent _intent = const ActivateIntent();
// Initial Selected Value
String dropdownvalue = 'Item 1';
// List of items in our dropdown menu
var items = [
'Item 1',
'Item 2',
'Item 3',
'Item 4',
'Item 5',
];
#override
void initState() {
super.initState();
SchedulerBinding.instance?.addPostFrameCallback((_) {
if (_dropdownButtonKey.currentContext != null) {
_dropdownButtonKey.currentContext?.visitChildElements((element) {
if (element.widget is Semantics) {
element.visitChildElements((element) {
if (element.widget is Actions) {
element.visitChildElements((element) {
Actions.invoke(element, _intent);
return;
});
}
});
}
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DropdownButton(
autofocus: true,
key: _dropdownButtonKey,
isDense: true,
isExpanded: true,
value: dropdownvalue,
icon: const Icon(Icons.keyboard_arrow_down),
items: items.map((String items) {
return DropdownMenuItem(
value: items,
child: Text(items),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
dropdownvalue = newValue!;
});
},
),
],
),
),
);
}
}

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

DropDownButton item not being selected

I am trying to put a DropdownButton on one of my screens. I have followed several examples but I can not get it to show the selected item. It keeps showing the first item in the list.
String _trxnStatus = 'Listed';
DropdownButton<String>(
hint: Text('Please choose transaction status'),
value: _trxnStatus,
onChanged: (value) {
setState(() {
_trxnStatus = value;
});
},
items: <String>['Listed', 'Under Contract', 'Closed'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
I have traced the value through the debugger. onChange works fine and shows the selected value. However, when it comes to mapping the list and returning the DropdownMenuItem the var value = 'Listed'.
How do I get this to work?
Thanks.
You are possibly initializing the _trxnStatus within the build function. You need to initialize _trxnStatus outside of the build function. Please see the working code below:
import 'package:flutter/material.dart';
final Color darkBlue = const Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String _trxnStatus = 'Listed';
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
hint: Text('Please choose transaction status'),
value: _trxnStatus,
onChanged: (value) {
setState(() {
_trxnStatus = value;
});
},
items: <String>['Listed', 'Under Contract', 'Closed']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}

DropdownButton doesn't re-render the menu when items change

DropdownButton doesn't reflect menuItem's changes when the dropdown menu is open.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: Center(
child: MyStatefulWidget(),
),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final disabledItems = ['Free', 'Four'];
List<String> items = ['One', 'Two', 'Free', 'Four'];
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.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String newValue) {
if (!disabledItems.contains(newValue)) {
setState(() {
dropdownValue = newValue;
});
}
},
items: items.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Row(children: [
Text(
value,
style: TextStyle(
color: disabledItems.contains(value) ? Colors.grey : null,
),
),
IconButton(
icon: Icon(Icons.delete),
color: Colors.black38,
onPressed: () {
setState(() {
items.removeWhere((element) => element == 'Two');
});
print(items.length);
},
)
]),
);
}).toList(),
);
}
}
What I aim is the chance of removing an item from the menu when the delete icon is pressed. All the expected events are working as expected and the DropDown items list is updating accordingly in the backend but it doesn't re-render.
DorpDown Menu with delete icon
In order to be able to see the updated items list I have to close the dropdown menu and open it again but this doesn’t feel right in terms of user experience.