When a `DropdownButtonFormField’ widget’s build method is executed, if the user has already clicked on it and the options are diplayed in the UI, it seems those options don’t get updated by the build method. I’ve tried various aproaches and asked the AIs to no avail; 🙁
Currently, the widget is
//...
import 'package:get_it/get_it.dart';
//...
class DynamicDropdown extends StatefulWidget {
final void Function(ClientLItem) onSelected;
const DynamicDropdown(
{super.key,
/*required Key key,*/ required this.onSelected}) /*: super(key: key)*/;
@override
_DynamicDropdownState createState() => _DynamicDropdownState();
}
class _DynamicDropdownState extends State<DynamicDropdown> {
String name = '_DynamicDropdownState';
ClientLItem? _selectedClient;
String dropdownValue = "0";
bool shouldUpdate = false;
ClientListicle _clientListicle = GetIt.I.get<ClientListicle>();
List<DropdownMenuItem<String>> menuItems = [
const DropdownMenuItem(value: "0", child: Text('Select')),
];
final _dropdownKey = GlobalKey<FormFieldState>();
List<DropdownMenuItem<String>>? get dropdownItems {
developer.log(
'get dropdownItems() clients have ${_clientListicle.items.length} clients',
name: name);
List<DropdownMenuItem<String>> newItems = [
const DropdownMenuItem(value: "0", child: Text('Select')),
];
for (var element in _clientListicle.items) {
DropdownMenuItem<String> potentialItem = DropdownMenuItem(
value: element.id.toString(), child: Text(element.title));
newItems.add(potentialItem);
developer.log('menu items count ${newItems.length}', name: name);
}
developer.log('new menu items count ${newItems.length}', name: name);
if (newItems.length <= 1) {
return null;
} else {
//if length of newitems differs from menu items schedule a forced rebuild
if (newItems.length != menuItems.length) {
menuItems = newItems;
shouldUpdate = true;
_onClientListicleUpdated();
_dropdownKey.currentState!.build(context);
}
return menuItems;
}
}
@override
void initState() {
developer.log('initState', name: name);
super.initState();
// Listen for changes in the ClientListicle instance
_clientListicle.addListener(_onClientListicleUpdated);
// if _clientListicle.items.isEmpty load some clients
WidgetsBinding.instance.addPostFrameCallback((_) async {
developer.log('addPostFrameCallback', name: name);
if (_clientListicle.items.isEmpty) {
fetchClients();
}
if (shouldUpdate) {
shouldUpdate = false;
setState(() {
// update state here
});
_dropdownKey.currentState!.reset();
}
});
}
@override
void dispose() {
// Remove the listener when the widget is disposed
_clientListicle.removeListener(_onClientListicleUpdated);
super.dispose();
}
void _onClientListicleUpdated() {
developer.log('_onClientListicleUpdated');
// Call setState to rebuild the widget when the ClientListicle instance is updated
setState(() {
dropdownItems;
});
_dropdownKey.currentState!.reset();
}
@override
State<StatefulWidget> createState() {
developer.log('createState()');
return _DynamicDropdownState();
}
@override
Widget build(BuildContext context) {
developer.log(
'Build ClientListicle has ${_clientListicle.items.length} items',
name: name);
developer.log('dropdownItems has ${dropdownItems?.length} items',
name: name);
if (shouldUpdate) {
developer.log('shouldUpdate is true', name: name);
shouldUpdate = false;
// Schedule a rebuild
setState(() {});
}
return DropdownButtonFormField<String>(
key: _dropdownKey,
value: dropdownValue,
icon: const Icon(Icons.keyboard_arrow_down),
items: dropdownItems,
validator: (value) => value == "0" ? 'Please select a client' : null,
onChanged: (String? newValue) {
if (newValue != null && newValue != "0") {
developer.log('selected newValue $newValue', name: name);
dropdownValue = newValue;
_selectedClient =
_clientListicle.getById(int.parse(newValue!)) as ClientLItem;
setState(() {});
widget.onSelected(_selectedClient!);
} else {
_selectedClient = null;
dropdownValue = "0";
}
_dropdownKey.currentState!.reset();
},
);
}
Future<void> fetchClients() async {
developer.log('fetchClients', name: name);
await _clientListicle.fetch(numberToFetch: 5);
}
}
Based on the log output I can see that e.g. [_DynamicDropdownState] Build ClientListicle has 3 items
, but still see only the single item that was available when I clicked on the dropdown before the data had arrived.
If I click outside the dropdown and reopen it the correct items appear, so setState inside the stateful widget appears not to force a rebuild of the options list in the UI.
3
Answers
You can try creating an StatefullWidget and putting your dropdown there then by calling its setState you should be able to update the dropdown menu items
Try making all the changes that you wish to happen inside
setState({<here>});
.I have a feeling that you call
setState
too early- when not all the properties were updated – this way your code is rebuild with updated properties always.empty setState issues
Unfortunatelly your code is lengthy and incomplete so there is no way to debug it.