Skip to main content

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.

info

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(() { ... });
}

Additional resources:

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 ?? "...");
},
),
);
}
}