use-setstate-synchronously
Warns when setState
is called past an await point (also known as asynchronous gap) within a subclass of State.
In async functions, the state of a widget may have been disposed between await points, e.g. because the user moved to another screen,
leading to errors calling setState
. After each await point, i.e. when a Future is awaited, the possibility
that the widget has been unmounted needs to be checked before calling setState
.
Consider storing Futures directly in your state and use FutureBuilder
to unwrap them.
If this is not possible, you can also check for mounted
to only update state when the widget is still mounted. However, an effective fix usually
does not make use of mounted
, but rather revolves around refactoring your states.
The following patterns are recognized when statically determining mountedness:
if (mounted)
if (mounted && ..)
if (!mounted || ..)
try
andswitch
mountedness per branch- Divergence in
for
,while
andswitch
statements usingbreak
orcontinue
If a !mounted
check diverges, i.e. ends in a return
or throw
, the outer scope is considered mounted and vice versa:
if (!mounted) return;
// Should be mounted right now
setState(() { ... });
// After this statement, need to check 'mounted' again
await fetch(...);
// In control flow statements, 'break' and 'continue' diverges
while (...) {
if (!mounted) break;
// Should be mounted right now
setState(() { ... });
}
⚙️ Config
Set methods
(default is setState
) to configure the list of methods to check for unchecked async calls.
dart_code_metrics:
rules:
- use-setstate-synchronously:
methods:
- setState
- yourMethod
Example
❌ Bad:
class _MyWidgetState extends State<MyWidget> {
String message;
Widget build(BuildContext context) {
return Button(
onPressed: () async {
String fromServer = await fetch(...);
// LINT: Avoid calling 'setState' past an await point without checking if the widget is mounted.
setState(() {
message = fromServer;
});
},
child: Text(message),
);
}
}
✅ Good:
class _MyWidgetState extends State<MyWidget> {
String message;
Widget build(BuildContext context) {
return Button(
onPressed: () async {
String fromServer = await fetch(...);
if (mounted) {
setState(() {
message = fromServer;
});
}
},
child: Text(message),
);
}
}
✅ Good:
class _MyWidgetState extends State<MyWidget> {
Future<String> message;
Widget build(BuildContext context) {
return Button(
onPressed: () {
setState(() {
message = fetch(...);
});
},
child: FutureBuilder<String>(
future: message,
builder: (context, snapshot) {
return Text(snapshot.data ?? "...");
},
),
);
}
}
Additional Resources
- https://stackoverflow.com/questions/49340116/setstate-called-after-dispose
use_build_context_synchronously
, a lint that checks for async usages ofBuildContext