Flutter setState() after completion of async function
Solution 1
Try this
Change _remove to future void
Future<void> _remove(int id) async {
DatabaseHelper helper = DatabaseHelper.instance;
await helper.deleteTransaction(id);
}
then change your setstate
setState(() {
_remove(transactionsReversed[index].id).then((_) {
setState(() {
//setstate
});
});
})
});
Solution 2
Your issue seems to be related to the fact that you are calling setState
immediately as delete is called, which means that the view rebuild is triggered straight away, before helper.deleteTransaction(id);
is called. I would suggest that you only call setState
after the future is returned from your async
_remove
method, like this:
Add the Future<void>
type to your _remove
method:
Future<void> _remove(int id) async {
DatabaseHelper helper = DatabaseHelper.instance;
await helper.deleteTransaction(id);
}
Call your setState
only once you receive the future as you can see below:
delete: (){
print('Running delete function on ${transactionsReversed[index].id}');
setState(() {
_remove(transactionsReversed[index].id).then((value) => setState((){}));
});
}
Solution 3
As mentioned in other answers, the problem was due to setState
running before the async
metod _remove
completion.
Since _remove
is a private method inside your Widget class, maybe it could take the setState
in charge.
Your _remove
becomes:
Future<void> _remove(int id) async {
await DatabaseHelper.instance.deleteTransaction(id);
setState((){});
}
And you can simplify your delete
callback to:
ListView.builder(
[...]
itemBuilder: (context, index) {
return CustomListItem(
[...]
delete: () => _remove(transactionsReversed[index].id),
);
}
),
Matze
Updated on December 28, 2022Comments
-
Matze over 1 year
I have created a ListView.builder of custom Widgets based on entries in a database. Now I am trying to add a delete function. When I click on the button, the entry gets removed from the database accordingly. However, the ListView does not update despite setState() being called. I suspect that this might be due to the delete method being async, how can I have the app first delete the entry and then refresh the ListView?
The code for my ListView.builder is:
ListView.builder( padding: const EdgeInsets.all(8.0), itemExtent: 106.0, itemCount: snapshot.data.length, itemBuilder: (context, index) { return CustomListItem( name: transactionsReversed[index].name, category: transactionsReversed[index].category, sign: transactionsReversed[index].sign, amount: transactionsReversed[index].amount, id: transactionsReversed[index].id, delete: (){ print('Running delete function on ${transactionsReversed[index].id}'); setState(() { _remove(transactionsReversed[index].id); }); } ); } ),
The custom list item looks like this:
class CustomListItem extends StatelessWidget { const CustomListItem({ this.name, this.category, this.sign, this.amount, this.id, this.delete }); final String name; final String category; final String sign; final double amount; final int id; final Function delete; @override Widget build(BuildContext context) { const double radius = 15.0; return Padding( padding: const EdgeInsets.symmetric(vertical: 5.0), child: InkWell( onTap: () { Navigator.pushNamed(context, '/category_viewer', arguments: { 'category': category }); }, child: Container( color: Colors.transparent, child: Container( decoration: BoxDecoration( color: Colors.black, borderRadius: new BorderRadius.only( topLeft: const Radius.circular(radius), topRight: const Radius.circular(radius), bottomLeft: const Radius.circular(radius), bottomRight: const Radius.circular(radius), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Expanded( flex: 1, child: IconButton( onPressed: delete, icon: Icon( Icons.delete_outlined, color: Colors.red, size: 40, ), color: Colors.red ), ), Expanded( flex: 3, child: Padding( padding: const EdgeInsets.fromLTRB(5.0, 0.0, 0.0, 0.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text( name, style: const TextStyle( fontWeight: FontWeight.w500, fontSize: 20.0, color: Colors.white ), ), const Padding(padding: EdgeInsets.symmetric(vertical: 2.0)), Text( category, style: const TextStyle( fontSize: 14.0, color: Colors.white ), ), ], ), ), ), Padding( padding: const EdgeInsets.fromLTRB(0, 0, 50, 0), child: Text( '$sign${amount.toStringAsFixed(2)}€', style: TextStyle( fontSize: 25.0, color: sign == '+' ? Colors.green : Colors.red ), ), ), ], ), ), ), ), ); } }
Finally, the remove method is simply:
_remove(int id) async { DatabaseHelper helper = DatabaseHelper.instance; await helper.deleteTransaction(id); }
-
aoiTenshi about 3 yearsI am not sure but maybe you can initialize a bool. Then, if bool is true you refresh the listview. It starts with false, when you delete the entry you may setState your bool to true and then refresh works?
-
-
Matze about 3 yearsThank you very much, that worked. I had to update my data again within the inner setState(), but that did the trick.
-
Thierry about 3 yearsI don't think the first
setState
is needed anymore. -
Randal Schwartz about 3 yearsIn general, an empty setState is a code smell.
-
Thierry about 3 yearsFully agree with that point. I tend to go even one step further and consider that having a setState is a code smell. But with the information provided in the question, I'm not sure what State Management architecture (if any) is in place.