Nesting ListView into DropdownMenu - flutter

I have created a custom ListView and I want to nest it into my Dropdownmenu as the item. Currently, I am getting the 'NoSuchMethodError : The method 'map' was called on null'. I can't seem to map it right. I am very new to Flutter.
The code for the ListView
class TheDetails extends StatelessWidget {
TheDetails({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(8.0),
itemExtent: 70.0,
children: <CustomListItem>[
CustomListItem(
accName: 'Salary Account',
accNumber: 'Savings XXX-X-XX563-9',
accBalance: '56,302.56',
),
CustomListItem(
accName: 'Salary Account 2',
accNumber: 'Savings XXX-X-XX563-9',
accBalance: '89,302.56',
),
CustomListItem(
accName: 'Vayu Meetang',
accNumber: 'Savings XXX-X-XX563-9',
accBalance: '100,505.56',
),
],
);
}
}
The code for the DropdownButton
class SelectAccount extends StatefulWidget {
#override
_SelectAccountState createState() => _SelectAccountState();
}
class _SelectAccountState extends State<SelectAccount> {
var dropdownValue;
var accountNames;
#override
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints.expand(width: 375.0, height: 120.0),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(20.0),
),
child: DropdownButton<String>(
value: dropdownValue,
underline: Container(
height: 2,
),
icon: Icon(Icons.keyboard_arrow_down,
color: Colors.blue[500],),
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: accountNames.map((item){
return DropdownMenuItem(
value: item,
child: TheDetails(),
);
}).toList(),
),
);
}
}

You're calling .map() on accountNames which is not initialized, and therefore null at least on the first build. So doing var accountNames = []; will solve the issue.
A cleaner way to add a widget for each member of a list is :
items: [
for(var name in accountNames)
DropdownMenuItem(value: name, child: TheDetails()),
]
EDIT: Putting everything together :
// in top-level or another file
class Account {
Account(this.name, this.number, this.balance);
String name;
String number;
String balance;
}
// in _SelectAccountState or a provider
var selectedAccount = 'Savings XXX-X-XX563-9';
var accounts = [
Account(
'Salary Account',
'Savings XXX-X-XX563-7',
'56,302.56',
),
Account(
'Salary Account 2',
'Savings XXX-X-XX563-8',
'89,302.56',
),
Account(
'Vayu Meetang',
'Savings XXX-X-XX563-9',
'100,505.56',
),
];
// In the build method
DropdownButton<String>(
value: selectedAccount,
items: [
for(var account in accounts)
DropdownMenuItem<String>(
value: account.number,
child: Text(account.name),),
],
onChanged: (val) => setState(() => selectedAccount = val)
),

Related

My Flutter ListView is always removing the last item from the list

I'm creating a Flutter Widget and when I try to remove an item from the list I'm using, it always removes the last one, I was thinking it could be a Key problem, but nothing suits it, do anyone know how I could solve this?
The code
create_game.dart
import 'package:flutter/material.dart';
import 'package:pontinho/components/custom_input.dart';
class CreateGame extends StatefulWidget {
const CreateGame({super.key});
#override
State<CreateGame> createState() => _CreateGameState();
}
class _CreateGameState extends State<CreateGame> {
List<String> names = [''];
void changeName(int nameIndex, String change) {
setState(() {
names[nameIndex] = change;
});
}
void removeName(int nameIndex) {
print(names);
print(nameIndex);
setState(() {
names.removeAt(nameIndex);
});
}
ListView createNamesInput() {
return ListView.builder(
itemCount: names.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
key: ObjectKey(index),
title: CustomInput(
key: ObjectKey(index),
labelText: "Nome",
onChanged: (String changed) => changeName(index, changed),
text: names[index],
onRemoved: () => removeName(index),
),
);
},
);
// return names
// .asMap()
// .entries
// .map((el) => CustomInput(
// key: ObjectKey('${el.key}'),
// labelText: "Nome",
// onChanged: changeName,
// index: el.key,
// text: names[el.key],
// onRemoved: removeName,
// ))
// .toList();
}
void addName() {
setState(() {
names.add('');
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: GestureDetector(
onTap: (() => Navigator.pop(context)),
child: const Icon(
Icons.arrow_back,
color: Colors.black,
size: 40,
),
),
backgroundColor: Colors.white,
titleTextStyle: const TextStyle(
color: Colors.black,
fontSize: 20,
),
title: const Text("CRIE SEU JOGO"),
),
body: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
// child: createNamesInput(),
child: Column(
children: [
createNamesInput(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: addName,
child: Row(
children: const [
Icon(Icons.add),
Text('Adicionar Jogador'),
],
),
),
],
),
),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () => print('Iniciar!'),
child: const Text('Iniciar!'),
),
)
],
),
),
);
}
}
custom_input.dart
import 'package:flutter/material.dart';
typedef OneArgumentCallback = void Function(String changed);
class CustomInput extends StatefulWidget {
final OneArgumentCallback onChanged;
final VoidCallback onRemoved;
final String labelText;
final String text;
const CustomInput({
super.key,
required this.onChanged,
required this.labelText,
required this.text,
required this.onRemoved,
});
#override
State<CustomInput> createState() => _CustomInputState();
}
class _CustomInputState extends State<CustomInput> {
late final TextEditingController inputController;
#override
void initState() {
super.initState();
inputController = TextEditingController(text: widget.text);
}
void changeContent(String value) {
widget.onChanged(
value,
);
}
#override
Widget build(BuildContext context) {
return TextFormField(
key: widget.key,
controller: inputController,
textDirection: TextDirection.ltr,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
labelText: widget.labelText,
suffixIcon: IconButton(
onPressed: () => widget.onRemoved(),
icon: const Icon(
Icons.close,
color: Colors.red,
),
),
),
autocorrect: false,
onChanged: (value) => changeContent(value),
);
}
}
Indeed it is a key issue, you have to create a combined key that must be unique for each item, I merged the index with names[index],
CustomInput(
key: ObjectKey('$index:${names[index]}'),
labelText: "Nome",
onChanged: (String changed) => changeName(index, changed),
text: names[index],
onRemoved: () => removeName(index),
),
note that if you try this code alone the textfield will lose focus because the key has changed, this will be solved by removing the setState inside the onChange
void changeName(int nameIndex, String change) {
names[nameIndex] = change;
}
here you don't need setState because the UI will be updated by default when you are typing in the textfield
I hope I made it clear
I was thinking it could be a Key problem
That's correct; You need to use names[index] as the value for your Key:
ListTile(
key: ObjectKey(names[index]),
title: CustomInput(

I have created a custom DropDownButton with GetX in Flutter, it throws me error when i wrap with obx and if i remove obx i cannot select from the list

Custom DropDownButton
class CustomDropDown extends StatefulWidget {
PurchasePackageController purchasePackageController =
PurchasePackageController();
final String hintName;
final List<String> listName;
final void Function(String?)? onChanged;
CustomDropDown(
{Key? key,
required this.hintName,
required this.listName,
required this.onChanged})
: super(key: key);
#override
State<CustomDropDown> createState() => _CustomDropDownState();
}
class _CustomDropDownState extends State<CustomDropDown> {
String? selectedValue;
List<DropdownMenuItem<String>> buildDropdownMenuItems(List list) {
List<DropdownMenuItem<String>> dropDownItems = [];
list.forEach((value) {
dropDownItems.add(DropdownMenuItem<String>(
value: value,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Text(
value,
style: TextStyle(fontSize: 16.0, color: colorPrimary),
),
),
));
});
return dropDownItems;
}
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0.h),
child: InputDecorator(
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: colorPrimary),
),
),
isEmpty: true,
//dropdownvalue == '',
child: DropdownButtonHideUnderline(
child: Obx(() => DropdownButton<String>(
iconSize: 30.0.sp,
iconEnabledColor: colorPrimary,
hint: Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0.h),
child: Text(
widget.hintName,
style: TextStyle(fontSize: 18.0.sp, color: colorPrimary),
),
),
value: selectedValue,
isDense: true,
onChanged: (value) => widget.onChanged,
buildDropdownMenuItems()
items: buildDropdownMenuItems(
widget.listName,
),
)),
),
),
);
}
}
View
class LocationView extends GetView<LocationController> {
LocationView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Location'),
centerTitle: true,
),
body: Column(
children: [
CustomDropDown(
hintName: "Package Type",
listName: controller.MDPackageType,
onChanged: (newValue) =>
controller.onChangedPackageName(newValue!),
),
CustomDropDown(
hintName: "Package Name",
listName: controller.MDPackageType,
onChanged: (newValue) =>
controller.onChangedPackageType(newValue!)),
Container(
height: MediaQuery.of(context).size.height * 0.50,
width: MediaQuery.of(context).size.width * 0.95,
child: Column(children: [
Text(controller.selectedPackageName.value),
Text(controller.selectedPackageType.value)
]),
)
],
));
}
}
Controller
class LocationController extends GetxController {
List<String> MDPackageType = [
'Package Type 1',
'Package Type 2',
'Package Type 3',
'Package Type 4',
];
List<String> MDPackageName = [
'Package Name 1',
'Package Name 2',
'Package Name 3',
'Package Name 4',
];
var selectedPackageType = "Package Type 1".obs;
var selectedPackageName = "Package Name 1".obs;
onChangedPackageType(String value) => selectedPackageType.value = value;
onChangedPackageName(String value) => selectedPackageName.value = value;
}
Error
throw """
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
And if i remove obx, i can code is running but i cannot select items frrom the Drop Down Menu
Fixed!!!
Had to change
onChanged: (value) => widget.onChanged,
to
onChanged: (value) {
setState(() {
selectedValue = value;
});
if (widget.onChanged != null) widget.onChanged!(value);
},
And remove Obx

Checkbox doesn't change when clicked in dropdownbutton

I am using DropdownButton and I am facing the following issue. I'm using a checkbox in elements, but when I click on an element, I don't get a checkmark indicating that the checkbox has been clicked. As a result, I need to close and reopen it, and then I will see the changes that were clicked on the "checkbox". The second problem is that when I select one element, all elements are selected for me. As a final result, I need to get so that I can select an element and the checkbox is immediately marked, if 2 elements are needed, then two, and so on. Tell me how to fix these problems, I will be grateful for the help?
dropdown
class DropdownWidget extends StatefulWidget {
List<String> items;
SvgPicture? icon;
double width;
DropdownWidget({
Key? key,
required this.items,
required this.icon,
required this.width,
}) : super(key: key);
#override
State<DropdownWidget> createState() => _DropdownWidgetState();
}
class _DropdownWidgetState extends State<DropdownWidget> {
String? selectedValue;
bool isChecked = false;
#override
void initState() {
super.initState();
if (widget.items.isNotEmpty) {
selectedValue = widget.items[1];
}
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
child: DropdownButtonHideUnderline(
child: DropdownButton2(
items: widget.items
.map((item) => DropdownMenuItem<String>(
value: item,
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: constants.Colors.white.withOpacity(0.1),
width: 1,
),
),
),
child: Center(
child: Row(
children: [
if (item == selectedValue)
const SizedBox(
width: 0,
),
Expanded(
child: Text(
item,
style: constants.Styles.smallTextStyleWhite,
),
),
Checkbox(
checkColor: Colors.black,
value: isChecked,
onChanged: (bool? value) {
setState(() {
isChecked = value!;
});
},
),
],
),
),
),
))
.toList(),
value: selectedValue,
onChanged: (value) {
setState(() {
selectedValue = value as String;
});
},
icon: SvgPicture.asset(constants.Assets.arrowDropdown),
iconSize: 21,
buttonHeight: 27,
itemHeight: 47,
dropdownMaxHeight: 191,
dropdownWidth: 140,
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: constants.Colors.purpleMain,
),
color: constants.Colors.greyDark,
),
selectedItemBuilder: (context) {
return widget.items.map(
(item) {
return Row(
children: [
widget.icon ?? const SizedBox(),
const SizedBox(width: 8),
Text(
item,
style: constants.Styles.bigBookTextStyleWhite,
),
],
);
},
).toList();
},
),
),
);
}
}
items
final List<String> items = const [
"All EV's",
'Main EV',
'<EV2>',
];
I hope this example explains the concept. For simplcity I made simple a new file, run it and see the results:
Then main idea in two lists, _checkList contain values of the CheckBox and _selectedList handles the main dropdown widget to show the selection.
Feel free to ask any questions and I'm happy to help
import 'package:flutter/material.dart';
class TestPage extends StatelessWidget {
const TestPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const AnimationDemo(number: 5);
}
}
class AnimationDemo extends StatefulWidget {
const AnimationDemo({Key? key, this.number = 2}) : super(key: key);
final int number;
#override
State<AnimationDemo> createState() => _AnimationDemoState();
}
class _AnimationDemoState extends State<AnimationDemo> {
late List<bool> _checkList;
late List<int> _selectedIndex;
bool _isOpen = false;
#override
void initState() {
_checkList = List.filled(widget.number, false);
_selectedIndex = <int>[];
super.initState();
}
List<DropDownItem> generateItems() {
var tmp = <DropDownItem>[];
for (var i = 0; i < _checkList.length; i++) {
tmp.add(DropDownItem(
isChecked: _checkList[i],
onChanged: (value) {
setState(() {
_checkList[i] = value!;
if (value && !_selectedIndex.contains(i)) {
_selectedIndex.add(i);
} else {
_selectedIndex.remove(i);
}
});
},
));
}
return tmp;
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: Text((_selectedIndex.isEmpty)
? 'Nothing Selected'
: _selectedIndex.join(',')),
),
GestureDetector(
onTap: () {
setState(() {
_isOpen = !_isOpen;
});
},
child: const Icon(Icons.arrow_downward),
),
],
),
AnimatedOpacity(
opacity: (_isOpen) ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: Column(
mainAxisSize: MainAxisSize.min,
children: generateItems(),
),
)
],
),
);
}
}
class DropDownItem extends StatelessWidget {
final bool isChecked;
final Function(bool?)? onChanged;
const DropDownItem({Key? key, this.onChanged, this.isChecked = false})
: super(key: key);
#override
Widget build(BuildContext context) {
return Row(
children: [
const Expanded(child: Text('Demo item')),
Checkbox(value: isChecked, onChanged: onChanged)
],
);
}
}
Here's how to achieve the Multiselect dropdown with DropdownButton2:
final List<String> items = [
'Item1',
'Item2',
'Item3',
'Item4',
];
List<String> selectedItems = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: DropdownButtonHideUnderline(
child: DropdownButton2(
isExpanded: true,
hint: Align(
alignment: AlignmentDirectional.center,
child: Text(
'Select Items',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).hintColor,
),
),
),
items: items.map((item) {
return DropdownMenuItem<String>(
value: item,
//disable default onTap to avoid closing menu when selecting an item
enabled: false,
child: StatefulBuilder(
builder: (context, menuSetState) {
final _isSelected = selectedItems.contains(item);
return InkWell(
onTap: () {
_isSelected
? selectedItems.remove(item)
: selectedItems.add(item);
//This rebuilds the StatefulWidget to update the button's text
setState(() {});
//This rebuilds the dropdownMenu Widget to update the check mark
menuSetState(() {});
},
child: Container(
height: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
_isSelected
? const Icon(Icons.check_box_outlined)
: const Icon(Icons.check_box_outline_blank),
const SizedBox(width: 16),
Text(
item,
style: const TextStyle(
fontSize: 14,
),
),
],
),
),
);
},
),
);
}).toList(),
//Use last selected item as the current value so if we've limited menu height, it scroll to last item.
value: selectedItems.isEmpty ? null : selectedItems.last,
onChanged: (value) {},
buttonHeight: 40,
buttonWidth: 140,
itemHeight: 40,
itemPadding: EdgeInsets.zero,
selectedItemBuilder: (context) {
return items.map(
(item) {
return Container(
alignment: AlignmentDirectional.center,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
selectedItems.join(', '),
style: const TextStyle(
fontSize: 14,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
),
);
},
).toList();
},
),
),
),
);
}
Also, I've added it as an example to the package doc "Example 4" so you can get back to it later.

How to implement dropdownbutton into a function in flutter

Im working on a company project. But I can't simply get the idea of how to implement a basic dropdown button into a function but I can't seem to make the values change in the dropdown function what do you think im doing wrong here's my code:
Widget buildDropdownField({
required String dropdownHeader,
required String dropdownValue,
}) {
return Column(
children: <Widget>[
Text(dropdownHeader),
const SizedBox(
height: 10,
),
//dropdownField
DropdownButton<String>(
value: dropdownValue,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
},
items: <String>['-', 'Geçti', 'Kaldı', 'Belirsiz']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)
],
);
}
Wrap with StatefulBuilder it will work.
Widget buildDropdownField(
{required String dropdownHeader, required String dropdownValue}) {
return Column(
children: <Widget>[
Text(dropdownHeader),
const SizedBox(
height: 10,
),
StatefulBuilder(
builder: (_, setDropState) {
return DropdownButton<String>(
value: dropdownValue,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
setDropState(() {
dropdownValue = newValue!;
});
},
items: <String>['-', 'Geçti', 'Kaldı', 'Belirsiz']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
},
)
],
);
}
Try below code hope its help to you. use StatefulBuilder Refer here
Your dropdown function:
buildDropdownField({
required String dropdownHeader,
required String dropdownValue,
}) {
return Column(
children: <Widget>[
Text(dropdownHeader),
const SizedBox(
height: 10,
),
//dropdownField
StatefulBuilder(builder: (context, StateSetter setState) {
return DropdownButton<String>(
value: dropdownValue,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
},
items: <String>['-', 'Geçti', 'Kaldı', 'Belirsiz']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
})
],
);
}
Your Widget:
buildDropdownField(
dropdownHeader: 'dropdownHeader',
dropdownValue: '-',
),
Result->
Result after selection->
First of all, you shouldn't update the parameter with the new value. It did update the parameter, but the function will still getting the value from it's calling.
I did not know the buildDropdownField function is inside a class or not, but it's okay and I will provide the solutions for both scenarios.
Within the Class
You need to create a variable within a class outside the functions.
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _dropDownValue = '-';
Widget buildDropdownField({required String dropdownHeader}) {
return Column(
children: <Widget>[
Text(dropdownHeader),
const SizedBox(
height: 10,
),
//dropdownField
DropdownButton<String>(
value: _dropDownValue,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
setState(() {
_dropDownValue = newValue!;
});
},
items: <String>['-', 'Geçti', 'Kaldı',
'Belirsiz'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)
],
);
}
}
Outside the Class
You need to turn it into Stateful Widget in order for the drop down text to change. Once the dropdown is a stateful widget, you can use the solution above or a callback to make the changes on parent class.
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _dropDownValue = '-';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: DropDownWidget(
dropdownHeader: 'Name',
dropdownValue: _dropDownValue,
onChanged: (String? newValue) {
setState(() {
_dropDownValue = newValue!;
});
},
),
),
);
}
}
class DropDownWidget extends StatefulWidget {
final String dropdownHeader;
final String dropdownValue;
final Function(String?)? onChanged;
DropDownWidget({required this.dropdownHeader, required this.dropdownValue, this.onChanged, Key? key}) : super(key: key);
#override
_DropDownWidgetState createState() => _DropDownWidgetState();
}
class _DropDownWidgetState extends State<DropDownWidget> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(widget.dropdownHeader),
const SizedBox(
height: 10,
),
//dropdownField
DropdownButton<String>(
value: widget.dropdownValue,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: widget.onChanged,
items: <String>['-', 'Geçti', 'Kaldı', 'Belirsiz'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)
],
);
}
}

Custom Widget not Rendering

I have made a custom DropDown Picker the problem is when I switch it, the widget does not get rendered
There are 2 Dropdowns on the UI. In different cases, the child dropDown may or may not be visible.
The problem only occurs if I have both parent and child dropdowns and in the next case, the two dropdowns are both visible.
These are the below cases of how my Dynamic UI is render
case 1 ) DropDown1 and Drop DropDown2 on the UI (Drop Down 2 is parent widget)
when the user clicks on dropDown 2 items the Main UI gets rendered.
(Drop Down 2 items Minutes, Hours, Day, Week)
DropDown 1 item changes as per drop down 2 )
class CustomDropDown extends StatefulWidget {
final List<String> dropDownList;
final defaultVal;
final Function selectedItem;
final customDropDownId;
final isExpanded;
final dropDownType;
const CustomDropDown(
{Key? key,
required this.dropDownList,
required this.defaultVal,
required this.selectedItem,
this.customDropDownId,
this.isExpanded = false,
required this.dropDownType})
: super(key: key);
#override
_CustomDropDownState createState() => _CustomDropDownState();
}
class _CustomDropDownState extends State<CustomDropDown> {
var chosenValue;
#override
void initState() {
super.initState();
print("initState");
chosenValue = widget.defaultVal;
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
#override
void dispose() {
super.dispose();
print("dispose");
}
#override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
print("dropDownList ${widget.dropDownList} defaultVal ${widget.defaultVal} chosenValue ${chosenValue} ");
if (widget.dropDownType == DropDownType.DROPDOWN_WITH_ARROW) {
return Material(
elevation: 10,
color: foreground_color,
borderRadius: BorderRadius.circular(10.r),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w),
child: DropdownButton<String>(
value: chosenValue,
isExpanded: widget.isExpanded,
dropdownColor: foreground_color,
icon: const Icon(Icons.keyboard_arrow_down_rounded),
borderRadius: BorderRadius.circular(10.r),
underline: const SizedBox(),
style: const TextStyle(color: Colors.white),
iconEnabledColor: Colors.white,
items: widget.dropDownList
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: const TextStyle(color: Colors.white),
),
);
}).toList(),
onChanged: (String? value) {
if (value != null) {
setState(() {
chosenValue = value;
widget.selectedItem(chosenValue, widget.customDropDownId);
});
}
},
),
),
);
}
Parent Widget
Widget repeatEveryWidget(chosenValue) {
if (chosenValue == dropDownJobList[0] ||
chosenValue == dropDownJobList[1]) {
bool isMinutesWidget = chosenValue == dropDownJobList[0];
List<String> dropDownList = isMinutesWidget ? minutesList : hourList;
return CustomDropDown(
isExpanded: false,
dropDownList: dropDownList,
defaultVal:
isMinutesWidget ? defaultMinuteSelected : defaulHourSelected,
dropDownType: DropDownType.DROPDOWN_WITH_ARROW,
selectedItem: (String selectedVal, DropDownsType dropDownId) {
if (isMinutesWidget) {
defaultMinuteSelected = selectedVal;
} else {
defaulHourSelected = selectedVal;
}
},
customDropDownId: DropDownsType.CustomId,
);
} else {
return const SizedBox();
}
}
Parent Calling
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text(REPEAT_EVERY),
SizedBox(
width: 10.w,
),
repeatEveryWidget(chosenValue),
SizedBox(
width: 10.w,
),
CustomDropDown(
dropDownList: dropDownCustomList,
defaultVal: chosenValue,
dropDownType: DropDownType.DROPDOWN_WITH_ARROW,
selectedItem:
(String selectedVal, DropDownsType dropDownId) {
setState(() {
chosenValue = selectedVal;
});
},
customDropDownId:
DropDownsTypeRepeatPicker,
),
],
),
)
Output
If the user selects item 1 Minute and then selects any item other than hours the child drop down gets removed from UI. But when the user selects hours after a minute the Items in Child widget renders but the defaultValue of this does not pick a new value it retains the old data that was picked in minutes as the UI has not been destroyed.
The answer to the above question lies in statement management in a flutter.
As there are two lists which has string object and some data are identical like "5","10"
const List<String> minutesListConfig = ['5', '10', '15', '30', '45', '60'];
const List<String> hourListConfig = ['1', '2', '3','4', '5', '6', '7','8', '9', '10', '11','12'];
As said by the flutter team every widget has an element that keeps track of its state whenever you try to render identical object type in dropdown list it will not render the new list.
If you want the widget tree to make sure to render the dropdown widget in 2 different ways then you will have to use KEY
In this case, the Object key can be used to make sure in background flutter makes 2 different dropdown lists for both the cases and does not render the dropdown as 1 widget.
return CustomDropDown(
key: ObjectKey(chosenValue),
isExpanded: false,
dropDownList: dropDownList,
defaultVal:
isMinutesWidget ? defaultMinuteSelected : defaulHourSelected,
dropDownType: DropDownType.DROPDOWN_WITH_ARROW,
selectedItem: (String selectedVal, DropDownsType dropDownId) {
if (isMinutesWidget) {
defaultMinuteSelected = selectedVal;
} else {
defaulHourSelected = selectedVal;
}
},
customDropDownId: DropDownsType.CustomId,
);
if (widget.dropDownType == DropDownType.DROPDOWN_WITH_ARROW) {
return Material(
elevation: 10,
color: foreground_color,
borderRadius: BorderRadius.circular(10.r),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w),
child: DropdownButton<String>(
key:widget.key
value: chosenValue,
isExpanded: widget.isExpanded,
dropdownColor: foreground_color,
icon: const Icon(Icons.keyboard_arrow_down_rounded),
borderRadius: BorderRadius.circular(10.r),
underline: const SizedBox(),
style: const TextStyle(color: Colors.white),
iconEnabledColor: Colors.white,
items: widget.dropDownList
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
value,
style: const TextStyle(color: Colors.white),
),
);
}).toList(),
onChanged: (String? value) {
if (value != null) {
setState(() {
chosenValue = value;
widget.selectedItem(chosenValue, widget.customDropDownId);
});
}
},
),
),
);
Flutter team video on keys
https://www.youtube.com/watch?v=kn0EOS-ZiIc