Skip to main content

use-setstate-synchronously

added in: 1.6.0
⚙️
Pro+
preset: recommended

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 and switch mountedness per branch
  • Divergence in for, while and switch statements using break or continue

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.

analysis_options.yaml
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