use-build-context-synchronously

Same as use_build_context_synchronously in Dart lints

Path
lint/correctness/use-build-context-synchronously
Default
On (recommended)
Origin
Dart lints
Domain
flutter
Rule
482 of 512
recommendedflutter

Flags BuildContext use after an asynchronous gap without a mounted guard.

A mounted guard must check the same BuildContext used after the gap. Function bodies are checked independently; callbacks passed to SDK async APIs that guarantee a mounted context are exempt.

Invalid

example.dartdart
import 'package:flutter/widgets.dart';

Future<void> work() async {}
Future<bool> ready() async => true;
Object? consume(BuildContext context) => null;

Future<void> unguarded(BuildContext context) async {
  await work();
  context.toString();
}

Future<void> wrongGuard(BuildContext context, BuildContext other) async {
  await work();
  if (!other.mounted) return;
  context.toString();
}

Future<void> guardRhs(BuildContext context) async {
  await work();
  if (context.mounted || consume(context) == null) {}
  if (!context.mounted && consume(context) == null) {}
}

Future<void> loopBackedge(BuildContext context, bool keepGoing) async {
  while (keepGoing) {
    context.toString();
    await work();
  }
}

Future<void> forUpdate(BuildContext context, bool keepGoing) async {
  for (; keepGoing; await work()) {
    context.toString();
  }
}

Future<void> tryPath(BuildContext context) async {
  try {
    await work();
  } catch (_) {
    context.toString();
  }
}

Future<void> finallyPath(BuildContext context) async {
  try {
    await work();
  } finally {
    context.toString();
  }
}

Future<void> switchUnmatched(BuildContext context, int value) async {
  switch (value) {
    case 1:
      await work();
  }
  context.toString();
}

Future<void> switchGuardGap(BuildContext context, int value) async {
  switch (value) {
    case 1 when await ready():
      context.toString();
  }
}

Future<void> switchGuardFalsePath(BuildContext context, int value) async {
  switch (value) {
    case 1 when await ready():
      break;
    default:
      context.toString();
  }
}

Future<void> nestedInternalGap(BuildContext context) async {
  Future<void> local() async {
    await work();
    context.toString();
  }
  local();
}

Future<void> classicForDoesNotReplaceOuter(BuildContext context) async {
  await work();
  for (int context = 0; context < 1; context++) {}
  context.toString();
}

Future<void> forInContext(List<BuildContext> contexts) async {
  for (final loopContext in contexts) {
    await work();
    loopContext.toString();
  }
}

Future<void> patternForInContext(List<(BuildContext, int)> entries) async {
  for (final (loopContext, _) in entries) {
    await work();
    loopContext.toString();
  }
}

Future<void> whileBodyDoesNotReplaceOuter(BuildContext context) async {
  await work();
  while (false) { int context = 0; }
  context.toString();
}

Future<void> doWhileBodyDoesNotReplaceOuter(BuildContext context) async {
  await work();
  do { int context = 0; } while (false);
  context.toString();
}

Future<void> closureCapturesForInContext(List<BuildContext> contexts) async {
  for (BuildContext loopContext in contexts) {
    final callback = () async {
      await work();
      loopContext.toString();
    };
    callback();
  }
}

Future<void> classicForContextShadow(
  BuildContext context,
  BuildContext other,
) async {
  await work();
  if (!context.mounted || !other.mounted) return;
  for (BuildContext context = other; false;) {
    context.toString();
  }
  context.toString();
}

Future<void> forInContextShadow(
  BuildContext context,
  List<BuildContext> contexts,
) async {
  await work();
  if (!context.mounted) return;
  for (BuildContext context in contexts) {
    context.toString();
  }
  context.toString();
}

Future<void> patternForInContextShadow(
  BuildContext context,
  List<(BuildContext, int)> entries,
) async {
  await work();
  if (!context.mounted) return;
  for (final (BuildContext context, _) in entries) {
    context.toString();
  }
  context.toString();
}

Future<void> whileBreakDoesNotProveMounted(BuildContext context) async {
  await work();
  while (!context.mounted) {
    break;
  }
  context.toString();
}

Future<void> doBodyFactDoesNotEscape(
  BuildContext context,
  BuildContext other,
) async {
  await work();
  if (!other.mounted) return;
  do {
    final BuildContext context = other;
    if (!context.mounted) return;
  } while (false);
  context.toString();
}

Future<void> conditionalBreakWhile(
  BuildContext context,
  bool keepGoing,
  bool stop,
) async {
  while (keepGoing) {
    await work();
    if (stop) break;
    if (!context.mounted) continue;
  }
  context.toString();
}

Future<void> conditionalContinueFor(
  BuildContext context,
  bool keepGoing,
  bool skip,
) async {
  for (; keepGoing;) {
    await work();
    if (skip) continue;
    if (!context.mounted) break;
  }
  context.toString();
}

Future<void> conditionalBreakDoWhile(
  BuildContext context,
  bool keepGoing,
  bool stop,
) async {
  do {
    await work();
    if (stop) break;
    if (!context.mounted) continue;
  } while (keepGoing);
  context.toString();
}

Future<void> reassignedContext(
  BuildContext context,
  BuildContext other,
) async {
  await work();
  if (!context.mounted || !other.mounted) return;
  context = other;
  context.toString();
}

Future<void> inferredAssignmentContext(BuildContext context) async {
  Object value = 0;
  value = context;
  await work();
  value.toString();
}

Future<void> nestedInferredAssignmentContext(BuildContext context) async {
  Object value = 0;
  {
    value = context;
  }
  await work();
  value.toString();
}

Future<void> patternReassignedContext(
  BuildContext context,
  BuildContext other,
) async {
  await work();
  if (!context.mounted || !other.mounted) return;
  (context, _) = (other, 0);
  context.toString();
}

Future<void> outerTargetedBreak(BuildContext context, bool stop) async {
  outer: while (true) {
    while (true) {
      if (stop) {
        await work();
        break outer;
      }
      return;
    }
  }
  context.toString();
}

Future<void> outerTargetedContinue(
  BuildContext context,
  bool keepGoing,
  bool skip,
) async {
  outer: while (keepGoing) {
    context.toString();
    while (true) {
      await work();
      if (skip) continue outer;
      return;
    }
  }
}

Future<void> patternDeclarationContext(BuildContext outer) async {
  final (context, _) = (outer, 1);
  await work();
  context.toString();
}

Future<void> ifCaseContext(BuildContext outer, Object value) async {
  if (value case (BuildContext context, _)) {
    await work();
    context.toString();
  }
}

Future<void> switchPatternContext(BuildContext outer, Object value) async {
  switch (value) {
    case (BuildContext context, _):
      await work();
      context.toString();
  }
}

Future<void> dynamicAssignmentPreservesContext(
  BuildContext context,
  dynamic value,
) async {
  context = value;
  await work();
  context.toString();
}

Future<void> branchAssignmentKeepsContextPath(
  BuildContext context,
  BuildContext other,
  bool clear,
) async {
  var value = context;
  if (clear) value = other;
  await work();
  value.toString();
}

Future<void> mutuallyExclusiveAssignmentStillMerges(
  BuildContext context,
  bool useContext,
) async {
  Object value = 0;
  if (useContext) {
    value = context;
  } else {
    value = 0;
  }
  await work();
  value.toString();
}

Future<void> caseBeforeLaterCase(BuildContext context, int value) async {
  switch (value) {
    case 0:
      break;
    case 1:
      await work();
  }
  context.toString();
}

Future<void> caseBeforeLaterGuard(BuildContext context, int value) async {
  switch (value) {
    case 0:
      context.toString();
      break;
    case 1 when await ready():
      break;
  }
}

Future<void> groupedCaseBeforeLaterGuard(
  BuildContext context,
  int value,
) async {
  switch (value) {
    case 0:
    case 1 when await ready():
      context.toString();
  }
}

Future<void> chainedSwitchContinue(BuildContext context, int value) async {
  switch (value) {
    first:
    case 0:
      continue second;
    second:
    case 1:
      await work();
      continue third;
    third:
    default:
      context.toString();
  }
}

class Holder {
  final BuildContext context;
  Holder(this.context);
}

Future<void> propertyUse(Holder holder) async {
  await work();
  holder.context.toString();
}

Future<void> blockPropertyShadow(Holder holder, Holder other) async {
  await work();
  if (!holder.context.mounted) return;
  {
    final Holder holder = other;
    holder.context.toString();
  }
  holder.context.toString();
}

Future<void> classicForPropertyShadow(Holder holder, Holder other) async {
  await work();
  if (!holder.context.mounted) return;
  for (Holder holder = other; false;) {
    holder.context.toString();
  }
  holder.context.toString();
}

Future<void> forInPropertyShadow(Holder holder, List<Holder> others) async {
  await work();
  if (!holder.context.mounted) return;
  for (final holder in others) {
    holder.context.toString();
  }
  holder.context.toString();
}

abstract class MyState extends State<StatefulWidget> {
  Future<void> stateUses() async {
    await work();
    this.context.toString();
    context!.toString();
  }

  Future<void> loopContextShadow(List<BuildContext> contexts) async {
    await work();
    if (!this.context.mounted) return;
    for (BuildContext context in contexts) {
      context.toString();
    }
  }

  Future<void> parameterContextShadow(BuildContext context) async {
    await work();
    if (!this.context.mounted) return;
    context.toString();
  }

  void closureContextShadow() {
    final callback = (BuildContext context) async {
      await work();
      if (!this.context.mounted) return;
      context.toString();
    };
    callback(this.context);
  }
}

Future<void> unrelatedMountedGetter(
  BuildContext context,
  UnrelatedMounted receiver,
) async {
  await work();
  if (!receiver.mounted) return;
  context.toString();
}

class UnrelatedMounted {
  bool get mounted => true;
}

Future<void> whileMountedConditionIsNotGuard(BuildContext context) async {
  await work();
  while (context.mounted) {
    context.toString();
    break;
  }
}

Future<void> doConditionGap(BuildContext context) async {
  do {
    context.toString();
  } while (await ready());
}

Future<void> tryBodyGuardDoesNotEscape(BuildContext context) async {
  await work();
  try {
    if (!context.mounted) return;
  } catch (_) {}
  context.toString();
}

Future<void> catchGapReachesFinally(BuildContext context) async {
  try {
    throw StateError('failure');
  } catch (_) {
    await work();
  } finally {
    context.toString();
  }
}

Future<void> switchSubjectGap(BuildContext context) async {
  switch (await ready()) {
    case true:
      context.toString();
    case false:
      break;
  }
}
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
8 await work();
9 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
19 await work();
20 if (context.mounted || consume(context) == null) {}
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
20 if (context.mounted || consume(context) == null) {}
21 if (!context.mounted && consume(context) == null) {}
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
25 while (keepGoing) {
26 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
32 for (; keepGoing; await work()) {
33 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
40 } catch (_) {
41 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
48 } finally {
49 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
57 }
58 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
63 case 1 when await ready():
64 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
72 default:
73 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
79 await work();
80 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
87 for (int context = 0; context < 1; context++) {}
88 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
93 await work();
94 loopContext.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
100 await work();
101 loopContext.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
107 while (false) { int context = 0; }
108 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
113 do { int context = 0; } while (false);
114 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
120 await work();
121 loopContext.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
167 }
168 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
277 outer: while (keepGoing) {
278 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
289 await work();
290 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
295 await work();
296 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
303 await work();
304 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
313 await work();
314 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
324 await work();
325 value.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
348 }
349 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
368 case 1 when await ready():
369 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
394 await work();
395 holder.context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
428 await work();
429 this.context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
429 this.context.toString();
430 context!.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
462 if (!receiver.mounted) return;
463 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
472 while (context.mounted) {
473 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
479 do {
480 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
488 } catch (_) {}
489 context.toString();
Do not use a `BuildContext` across an `async` gap without a matching `mounted` check.
497 } finally {
498 context.toString();

Valid

example.dartdart
import 'package:flutter/widgets.dart';

Future<void> work() async {}
Object? consume(BuildContext context) => null;

Future<void> guarded(BuildContext context) async {
  await work();
  if (!context.mounted) return;
  context.toString();
}

Future<void> positiveBranch(BuildContext context) async {
  await work();
  if (context.mounted) {
    context.toString();
  }
}

Future<void> guardedBooleanRhs(BuildContext context) async {
  await work();
  if (context.mounted && consume(context) == null) {}
  if (!context.mounted || consume(context) == null) {}
}

Future<void> beforeGap(BuildContext context) async {
  context.toString();
  await work();
}

Future<void> matchingReceiver(BuildContext first, BuildContext second) async {
  await work();
  if (!first.mounted) return;
  first.toString();
  if (second.mounted) {
    second.toString();
  }
}

Future<void> irrefutableGuardFallthrough(BuildContext context, int value) async {
  await work();
  switch (value) {
    case _ when !context.mounted:
      break;
    default:
      context.toString();
  }
}

Future<void> terminatingSwitch(BuildContext context, int value) async {
  switch (value) {
    case 1:
      await work();
      return;
    default:
      break;
  }
  context.toString();
}

Future<void> outerGapDoesNotEnterClosure(BuildContext context) async {
  await work();
  final callback = () {
    context.toString();
  };
  callback();
}

Future<void> targetedContinue(BuildContext context, bool keepGoing) async {
  outer: while (keepGoing) {
    if (context.mounted) continue outer;
    break outer;
  }
}

Future<void> forInShadowsContext(BuildContext context) async {
  await work();
  for (int context in <int>[1]) {
    context.toString();
  }
}

Future<void> patternForInShadowsContext(BuildContext context) async {
  await work();
  for (final (int context, _) in <(int, int)>[(1, 2)]) {
    context.toString();
  }
}

Future<void> guardedOuterSurvivesClassicFor(BuildContext context) async {
  await work();
  if (!context.mounted) return;
  for (int context = 0; context < 1; context++) {
    context.toString();
  }
  context.toString();
}

Future<void> classicForMountedCondition(BuildContext context) async {
  await work();
  for (; context.mounted;) {
    context.toString();
    break;
  }
}

Future<void> doWhileMountedExit(BuildContext context) async {
  await work();
  do {} while (!context.mounted);
  context.toString();
}

Future<void> guardedGapBranch(BuildContext context, bool flag) async {
  if (flag) {
    await work();
    if (!context.mounted) return;
  }
  context.toString();
}

Future<void> repeatedWhileGuard(BuildContext context, bool keepGoing) async {
  await work();
  while (keepGoing) {
    if (!context.mounted) return;
    context.toString();
  }
}

Future<void> repeatedClassicForGuard(
  BuildContext context,
  bool keepGoing,
) async {
  await work();
  for (; keepGoing;) {
    if (!context.mounted) return;
    context.toString();
  }
}

Future<void> repeatedDoWhileGuard(BuildContext context, bool keepGoing) async {
  await work();
  do {
    if (!context.mounted) return;
    context.toString();
  } while (keepGoing);
}

Future<void> unreachableAfterConditionlessFor(BuildContext context) async {
  for (;;) {
    await work();
  }
  context.toString();
}

Future<void> multiDeclaratorShadow(BuildContext context) async {
  await work();
  {
    var context = 0, value = context.toString();
    value.toString();
  }
}

Future<void> unbracedIfScope(
  BuildContext context,
  BuildContext other,
  bool replace,
) async {
  await work();
  if (!context.mounted) return;
  if (replace) { int context = 0; }
  context.toString();
}

Future<void> tryCatchFinallyScopes(
  BuildContext context,
  BuildContext other,
) async {
  await work();
  if (!context.mounted) return;
  try {
    final int context = 0;
    context.toString();
  } on Object catch (context, stackTrace) {
    context.toString();
    stackTrace.toString();
  } finally {
    final int context = 0;
    context.toString();
  }
  context.toString();
}

Future<void> nonContextIfCaseShadow(BuildContext context, Object value) async {
  await work();
  if (value case (int context, _)) {
    context.toString();
  }
}

Future<void> nonContextSwitchShadow(BuildContext context, Object value) async {
  await work();
  switch (value) {
    case (int context, _):
      context.toString();
  }
}

Future<void> unreachableAfterReturningSwitch(
  BuildContext context,
  int value,
) async {
  await work();
  switch (value) {
    case 0:
      return;
    default:
      return;
  }
  context.toString();
}

Future<void> returningAssignmentDoesNotLeak(
  BuildContext context,
  bool replace,
) async {
  Object value = 0;
  if (replace) {
    value = context;
    return;
  }
  await work();
  value.toString();
}

Future<void> mutuallyExclusiveAssignment(
  BuildContext context,
  bool useContext,
) async {
  Object value = 0;
  if (useContext) {
    value = context;
  } else {
    await work();
    value.toString();
  }
}

abstract class MyState extends State<StatefulWidget> {
  Future<void> guardedState() async {
    await work();
    if (!mounted) return;
    context.toString();
    this.context.toString();
  }

  Future<void> nonContextLoopShadow() async {
    await work();
    for (int context in <int>[1]) {
      context.toString();
    }
  }

  void localFunctionContextShadow() {
    Future<void> context() async {
      await work();
      context();
    }
    context();
  }

  void nonContextClosureShadow() {
    final callback = (int context) async {
      await work();
      context.toString();
    };
    callback(1);
  }

  void unbracedIfLocalFunctionShadow(bool enabled) {
    if (enabled)
      Future<void> context() async {
        await work();
        context();
      }
  }

  void unbracedLoopLocalFunctionShadow() {
    while (false)
      Future<void> context() async {
        await work();
        context();
      }
  }

  void labeledLocalFunctionShadow() {
    declaration:
    Future<void> context() async {
      await work();
      context();
    }
  }
}

// The official rule matches the mounted getter, not receiver identity.
Future<void> otherContextMountedGetter(
  BuildContext context,
  BuildContext other,
) async {
  await work();
  if (other.mounted) {
    context.toString();
  }
}

Future<void> declaredObjectDoesNotBecomeContext(BuildContext context) async {
  Object value = context;
  await work();
  value.toString();
}

Future<void> declaredDynamicDoesNotBecomeContext(BuildContext context) async {
  dynamic value = 0;
  value = context;
  await work();
  value.toString();
}

Future<void> finallyGuardEscapes(BuildContext context) async {
  try {
    await work();
  } finally {
    if (!context.mounted) return;
  }
  context.toString();
}

Future<void> catchGuardAppliesInsideCatch(BuildContext context) async {
  try {
    await work();
  } catch (_) {
    if (!context.mounted) return;
    context.toString();
  }
}

Future<void> switchMountedWhenGuard(BuildContext context, int value) async {
  await work();
  switch (value) {
    case 0 when context.mounted:
      context.toString();
    default:
      break;
  }
}

Future<void> doBodyGapIsObserved(BuildContext context) async {
  do {
    context.toString();
    await work();
  } while (false);
}

abstract class OtherMountedState extends State<StatefulWidget> {
  Future<void> otherStateGetterGuardsStateContext(
    State<StatefulWidget> other,
  ) async {
    await work();
    if (!other.mounted) return;
    context.toString();
  }
}

Future<void> infiniteLoopMountedExit(BuildContext context) async {
  await work();
  if (!context.mounted) {
    while (true) {}
  }
  context.toString();
}

Never stop() => throw StateError('stopped');

Future<void> neverCallMountedExit(BuildContext context) async {
  await work();
  if (!context.mounted) stop();
  context.toString();
}

Configure use-build-context-synchronously

Set its severity in falcon.json. See the configuration guide.

falcon.jsonjson
{
  "linter": {
    "rules": {
      "correctness": {
        "use-build-context-synchronously": "error"
      }
    }
  }
}

Or silence one occurrence with a suppression comment; the reason is required.

example.dartdart
// falcon-ignore lint/correctness/use-build-context-synchronously: <reason>