check_is_not_closed_after_async_gap
This rule flags an emit(...) call that happens after an await inside a Bloc or Cubit, without an intervening if (isClosed) return; guard.
Why use this rule
Section titled “Why use this rule”A bloc can be closed while an asynchronous handler is suspended — the user navigates away mid-request, or the provider holding the bloc is disposed. When the awaited future finally completes, the handler resumes and emits into a bloc that no longer exists, which throws a StateError.
The failure is easy to miss: the throw happens inside a detached future, so it usually surfaces as an unhandled async error in the console rather than a crash you can trace. In tests it often does not appear at all.
Guarding with if (isClosed) return; after each await makes the handler exit cleanly instead.
This is the bloc counterpart to use_ref_and_state_synchronously and to Dart’s own use_build_context_synchronously.
See also: bloc: BlocBase.isClosed
class UserCubit extends Cubit<UserState> { UserCubit() : super(UserState.initial());
Future<void> load() async { final user = await repository.fetchUser(); // The cubit may have been closed while this was suspended emit(UserState.loaded(user)); }}class UserCubit extends Cubit<UserState> { UserCubit() : super(UserState.initial());
Future<void> load() async { final user = await repository.fetchUser(); if (isClosed) return; emit(UserState.loaded(user)); }}A guard resets the tracking, so a second await after it needs its own guard:
Future<void> loadTwice() async { final a = await repository.fetchA(); if (isClosed) return; emit(UserState.loaded(a));
final b = await repository.fetchB(); if (isClosed) return; // needed again emit(UserState.loaded(b));}Known limitations
Section titled “Known limitations”Both guard shapes are recognised: the early return if (isClosed) return; and the inverted wrapper if (!isClosed) { emit(...); }.
Statements are scanned in source order within one function body. An emit inside a nested closure is not reported, because that closure runs on its own schedule and its guards cannot be reasoned about from the enclosing scope. A guard hidden behind a helper method — if (_shouldStop()) return; — is also not recognised.
Turning this rule off
Section titled “Turning this rule off”This rule is in the opinionated preset, so it is on with
preset: opinionated, or by name:
rules: check_is_not_closed_after_async_gap: trueTo turn it off again:
rules: check_is_not_closed_after_async_gap: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Options
Section titled “Options”many_lints: rules: check_is_not_closed_after_async_gap: additional_methods: [safeEmit]rules: check_is_not_closed_after_async_gap: additional_methods: [safeEmit]| Option | Type | Default | Description |
|---|---|---|---|
additional_methods |
list of strings | [] |
Extra methods treated like emit, for a project wrapper that forwards to it |
Related rules
Section titled “Related rules”require_atomic_async_updates— Re-read shared state after an await instead of writing back a stale value.use_ref_and_state_synchronously— Check ref.mounted before using ref or state after an await.use_ref_read_synchronously— Add a mounted guard before calling ref.read after an await.use_setstate_synchronously— Guard setState after an await with a mounted check.