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.