I was working with DropDownButtonFormField2
where the items come from the API call.
The problem is, when the API call is finished, the dropdown list items are not updated.
After the API call is finished, I already called setState()
. But the item list is not updated, I need to go to other screen and go back to refresh the dropdown.
I am using mobx
btw.
Any help would be appreciated. Thank you
my_screen.dart
class MyScreenState extends State<MyScreen> {
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
key: _scaffoldKey,
body: _buildItemDropDown(),
),
);
}
@override
void initState() {
super.initState();
_my_store.getItemList().then((value) {
setState(() {});
});
}
_buildItemDropDown() {
return DropdownButtonHideUnderline(
key: UniqueKey(),
child: DropdownButtonFormField2(
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: ColorPalette.green[400]!)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: ColorPalette.black[200]!)),
),
isExpanded: true,
hint: const Text(
'Options',
style: TextStyle(fontSize: 14),
),
iconOnClick: SvgPicture.asset(
'assets/svg/arrow_drop_down_activated.svg',
),
icon: SvgPicture.asset(
'assets/svg/arrow_drop_down.svg',
),
iconSize: 30,
isDense: true,
buttonHeight: 40,
buttonPadding: const EdgeInsets.only(left: 12, right: 12),
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
),
items: obsItemSpec.asMap().entries.map((item) {
return DropdownMenuItem<String>(
value: item.value,
enabled: false,
child: StatefulBuilder(
builder: (context, menuSetState) {
final _isSelected = selectedItems.contains(item.value);
return InkWell(
onTap: () {
_isSelected
? selectedItems.remove(item.value)
: selectedItems.add(item.value);
setState(() {});
menuSetState(() {});
},
child: Container(
color: (item.key % 2 != 0)
? ColorPalette.white[400]
: ColorPalette.white[500],
height: double.infinity,
padding:
const EdgeInsets.symmetric(horizontal: 18.0, vertical: 0),
child: Row(
children: [
_isSelected
? SvgPicture.asset(
'assets/svg/checkbox_green_outlined.svg',
)
: SvgPicture.asset(
'assets/svg/checkbox_outlined_blank.svg',
),
const SizedBox(width: 10),
Text(
item.value,
style: const TextStyle(
fontSize: 14,
),
),
],
),
),
);
},
),
);
}).toList(),
itemPadding: EdgeInsets.zero,
value: selectedItems.isEmpty ? null : selectedItems.last,
validator: (value) {
if (value == null) {
return 'Please select gender.';
}
},
onChanged: (value) {},
selectedItemBuilder: (context) {
return obsItemSpec.map(
(item) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 0.0),
child: Text(
selectedItems.join(', '),
style: const TextStyle(
fontSize: 14,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
),
);
},
).toList();
},
));
}
my_store.dart
part 'my_store.g.dart';
class MyStore = _MyStore with _$MyStore;
abstract class _MyStore with Store {
@observable
Observable<List<ItemSpec>?> obsItemSpec = Observable<List<ItemSpec>?>(null);
@action
Future getItemList() async {
final future = _useCase.getItemList();
future.then((itemList) {
obsItemSpec = Observable(itemList.map((list) =>
ItemSpec(list.name, list.id)).toList());
});
}
CodePudding user response:
I think that your then
method inside of initState
executes before your future.then
is done inside of getItemList
method because you are not awaiting your future inside it nor returning it. So I suggest something like this:
@action
Future getItemList() async {
final itemList = await _useCase.getItemList();
obsItemSpec = Observable(
itemList.map((list) => ItemSpec(list.name, list.id)).toList());
}
CodePudding user response:
SetState() in initState won't work properly all the time. You can move function _my_store.getItemList().then((value) { setState(() {}); });
to didChangeDependencies()
which gets called after full state has been set, or better yet wrap _buildItemDropDown()
in futureBuilder and pass _my_store.getItemList()
as said future. Until connection done you can display loading indicator of disabled dropdown and after data is received futureBuilder will rebuild to show proper dropdown with received info.
CodePudding user response:
hi i suggest instead of use
_my_store.getItemList().then((value) {
setState(() {});
});
make function like :
getStoreData() async {
await _my_store.getItemList();
setState(() {});
}
then call it inside initState:
@override
void initState() {
super.initState();
getStoreData();
}
if you get error like you called setstate before or during build do:
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
getStoreData();
});
}