use-setstate-synchronously
added in: 1.0.0 warning
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 areturn
orthrow
, 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(() { ... });
}
Additional resources:
- https://stackoverflow.com/questions/49340116/setstate-called-after-dispose
[use_build_context_synchronously](https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html)
, a lint that checks for async usages of `BuildContext`
Config
Set methods
(default is setState
) to configure the list of methods to check for unchecked async calls.
dart_code_linter:
...
rules:
...
- use-setstate-synchronously:
methods:
- setState
- yourMethod
Example
Bad:
class _MyWidgetState extends State<MyWidget> {
String message;
@override
Widget build(BuildContext context) {
return Button(
onPressed: () async {
String fromServer = await fetch(...);
// LINT
setState(() {
message = fromServer;
});
},
child: Text(message),
);
}
}
Good:
class _MyWidgetState extends State<MyWidget> {
String message;
@override
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;
@override
Widget build(BuildContext context) {
return Button(
onPressed: () {
setState(() {
message = fetch(...);
});
},
child: FutureBuilder<String>(
future: message,
builder: (context, snapshot) {
return Text(snapshot.data ?? "...");
},
),
);
}
}