Using BuildContext Safely in Asynchronous Code
Problem
When working with Flutter, you encounter a common lint warning: "Do not use BuildContexts across async gaps." This occurs when you try to use a BuildContext
after an asynchronous operation, but the widget may no longer be in the widget tree.
The core issue is that BuildContext
becomes invalid if the widget is removed from the tree during the async operation, leading to potential crashes when trying to access navigation methods or other context-dependent functionality.
Why This Matters
Using a stale BuildContext
can cause:
- App crashes with "setState() called after dispose()" errors
- Navigation failures when trying to push/pop routes
- Memory leaks from holding references to disposed widgets
- Unexpected behavior in UI updates
Solutions
1. Check mounted
Property (Recommended)
Since Flutter 3.7, BuildContext
includes a mounted
property that works in both StatefulWidget
and StatelessWidget
:
Future<void> myAsyncMethod(BuildContext context) async {
await someFuture();
if (context.mounted) {
Navigator.of(context).pop();
}
}
WARNING
In StatefulWidgets, you can use the mounted
property directly from the state class, but prefer context.mounted
for consistency across both widget types.
2. Pass Context as Parameter (Best Practice)
Avoid storing BuildContext
in custom classes. Instead, pass it as a parameter when needed:
class MyCustomClass {
Future<void> myAsyncMethod(BuildContext context) async {
await Future.delayed(const Duration(seconds: 2));
if (context.mounted) {
Navigator.of(context).pop();
}
}
}
// Usage in widget
IconButton(
onPressed: () => MyCustomClass().myAsyncMethod(context),
icon: const Icon(Icons.action),
);
3. Extract Navigation Objects Before Async Operations
Capture navigation-related objects before the async gap:
void myAsyncMethod() async {
final navigator = Navigator.of(context);
await someFuture();
navigator.pop(); // Safe to use
}
4. Use Callbacks for Complex Scenarios
For more complex scenarios, use callbacks to handle post-async operations:
class MyCustomClass {
Future<void> myAsyncMethod(VoidCallback onSuccess) async {
await Future.delayed(const Duration(seconds: 2));
onSuccess.call();
}
}
// In your widget
MyCustomClass().myAsyncMethod(() {
if (mounted) {
Navigator.of(context).pop();
}
});
Common Pitfalls to Avoid
DANGER
Never disable the lint rule instead of fixing the underlying issue:
# DON'T disable this warning
linter:
rules:
use_build_context_synchronously: false
WARNING
Avoid storing BuildContext
in class fields or global variables. This creates hard-to-debug issues when the context becomes stale.
When to Use Which Approach
Approach | Best For | Considerations |
---|---|---|
context.mounted | Simple async operations | Works in all widget types |
Passing context as parameter | Custom classes | Prevents storing stale context |
Extracting navigation objects | Navigation-focused code | Limited to navigation operations |
Callbacks | Complex async workflows | Requires careful state management |
Complete Example
Here's a complete implementation showing the recommended approach:
class ApiService {
Future<void> fetchDataAndNavigate(
BuildContext context,
String endpoint
) async {
// Show loading dialog
showDialog(
context: context,
builder: (context) => const CircularProgressIndicator(),
);
// Simulate API call
await Future.delayed(const Duration(seconds: 2));
// Check if context is still valid
if (context.mounted) {
Navigator.of(context).pop(); // Close dialog
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ResultsPage()),
);
}
}
}
class MyWidget extends StatelessWidget {
final ApiService apiService = ApiService();
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => apiService.fetchDataAndNavigate(context, '/data'),
child: const Text('Fetch Data'),
);
}
}
Key Takeaways
- Always check
context.mounted
before usingBuildContext
after async operations - Never store
BuildContext
in class fields - pass it as a parameter instead - The lint rule exists to prevent hard-to-debug crashes
- Flutter 3.7+ provides
context.mounted
for both Stateful and Stateless widgets - Prefer solutions that keep context usage localized and predictable
By following these patterns, you can safely work with asynchronous operations while avoiding common pitfalls with BuildContext
usage.