Skip to content

avoid_late_context

v1.0.0WarningConfigurableState Management

This rule flags a late field inside a State whose initializer reads context. The value is computed once, at an unpredictable moment, and then never updates.

A late field initializes on first access. Inside a State that is usually during build, but nothing guarantees it — if the field is first touched from initState, the inherited-widget lookup runs before the element is mounted and throws.

The quieter problem is worse. A late field initializes exactly once. A value derived from Theme.of(context) or MediaQuery.of(context) freezes at whatever it was on first access and then ignores every later change: a theme switch, a rotation, a locale change, a parent rebuilding with new data. The UI keeps rendering stale values with nothing to indicate why.

Inherited widgets are designed to be read where they can be re-read — in build, or in didChangeDependencies when the value must be cached.

See also: Flutter: BuildContext, State.didChangeDependencies

A theme cached in a late final field. It initializes the first time build touches it and then never changes — flip the app to dark mode and this widget keeps painting the light palette:

class _CardState extends State<Card> {
late final ThemeData _theme = Theme.of(context);
@override
Widget build(BuildContext context) =>
Text(widget.title, style: _theme.textTheme.titleLarge);
}

The same field read from initState fails harder — the element is not mounted yet, so the lookup throws:

class _CardState extends State<Card> {
final Analytics _analytics = Analytics();
late final ThemeData _theme = Theme.of(context);
@override
void initState() {
super.initState();
_analytics.screenOpened(_theme.brightness.name); // throws here
}
@override
Widget build(BuildContext context) => Text(widget.title);
}

Read it in build, where it re-reads on every rebuild:

class _CardState extends State<Card> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Text(widget.title, style: theme.textTheme.titleLarge);
}
}

When the value must be cached, use didChangeDependencies — Flutter calls it again whenever an inherited dependency changes, so the cache stays current:

class _CardState extends State<Card> {
late ThemeData _theme;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_theme = Theme.of(context);
}
@override
Widget build(BuildContext context) =>
Text(widget.title, style: _theme.textTheme.titleLarge);
}

Only fields inside a State are checked, since elsewhere context is not the ambient widget context this rule is about. Classes that act as state without extending State can be included with the shared state_base_classes option.

Static fields are skipped, and so are late fields with no initializer — those are assigned explicitly, where the author controls the timing:

class _CardState extends State<Card> {
late ThemeData _theme; // not reported — nothing runs at first access
@override
void didChangeDependencies() {
super.didChangeDependencies();
_theme = Theme.of(context);
}
@override
Widget build(BuildContext context) => Text(widget.title);
}

The initializer must mention context by name and resolve to a BuildContext, so an unrelated local named context does not trigger the rule.

This rule is in the recommended preset, so it is on with preset: recommended or preset: opinionated. Add it to preset: core with avoid_late_context: true.

To turn it off:

many_lints.yaml
rules:
avoid_late_context: false

To keep the rule on but skip certain paths, use per-rule exclude.

Projects with a state abstraction that does not extend Flutter’s State can opt that base class into this rule:

analysis_options.yaml
many_lints:
rules:
avoid_late_context:
state_base_classes: [AppState]
Option Type Default Description
state_base_classes list of strings [] Additional non-State base classes whose subclasses should be treated as state classes