| name | async-await-null-race-condition |
| description | Fix "Null check operator used on a null value" errors when an object is set to null
during an async await. Use when: (1) Object reference is nullified while awaiting,
(2) Code accesses object with ! after await returns, (3) Cancel/dispose operations
run concurrently with async operations on same object. Solution: capture local
reference before await.
|
| author | Claude Code |
| version | 1.0.0 |
| date | "2026-02-01T00:00:00.000Z" |
Async Await Null Race Condition
Problem
When an async operation is awaiting, external code can set the object reference to null.
When the await completes, accessing the object with ! throws "Null check operator
used on a null value".
Context / Trigger Conditions
- Error:
Null check operator used on a null value
- Pattern:
await someObject!.asyncMethod() followed by someObject!.property
- Object can be nullified by cancel/dispose/cleanup operations
- Concurrent operations on shared mutable state
- Typically seen with session objects, connection handlers, or service references
Solution
Wrong - Object can become null during await:
Future<Result> waitForResponse() async {
// _session might be set to null while we're awaiting
final result = await _session!.waitForConnection();
if (result == null) {
// BUG: _session could be null here!
final state = _session!.state; // Throws null check error
...
}
}
void cancel() {
_session?.cancel();
_session = null; // This runs while waitForResponse is awaiting
}
Correct - Capture local reference before await:
Future<Result> waitForResponse() async {
// Capture reference BEFORE await
final session = _session!;
final result = await session.waitForConnection();
// Check if cancelled during await
if (_session == null) {
return Result.cancelled();
}
if (result == null) {
// Safe - using captured local reference
final state = session.state;
...
}
}
Verification
- Trigger the cancel/dispose operation while async operation is in progress
- No null check errors should occur
- Operation should gracefully handle cancellation
Example - Full Pattern
class ConnectionManager {
Session? _session;
Future<ConnectionResult> connect() async {
if (_session == null) {
return ConnectionResult.failure('No session');
}
// Capture before await
final session = _session!;
final response = await session.waitForResponse(
timeout: Duration(minutes: 2),
);
// Check if cancelled during await
if (_session == null) {
return ConnectionResult.failure('Cancelled');
}
// Safe to use captured reference
if (response == null) {
return ConnectionResult.failure(session.errorMessage);
}
return ConnectionResult.success(response);
}
void cancel() {
_session?.cancel();
_session?.dispose();
_session = null;
}
}
Notes
- This pattern applies to any mutable reference that can be nullified externally
- Common in Flutter/Dart with dispose patterns, Riverpod providers, and WebSocket sessions
- The captured local reference keeps the object alive during the await
- Always add a null check after await to detect cancellation
- Consider using
Completer cancellation patterns for more complex scenarios
Related Patterns
riverpod-ref-read-in-dispose - Similar issue with Riverpod ref lifecycle
flutter-dispose-timer-test-failure - Timer callbacks after disposal