Zones
Understand how to manage and control code execution using Zones in Dart.
What is it?
Zones are an execution context that allows you to intercept and control code execution. They provide a way to manage asynchronous operations, handle errors globally, and add cross-cutting concerns like logging and profiling without modifying existing code.
Why does it exist?
Zones exist to:
- Intercept asynchronous operations
- Handle uncaught errors globally
- Add logging and monitoring
- Manage execution context (like HTTP headers)
- Enable debugging and profiling
- Control code execution flow
Basic Zones
Creating a Zone
A Zone is created by forking the current zone. Each zone can have custom behavior specified through
ZoneSpecification.
import 'dart:async';
void main() {
print('Outside zone');
// Create a zone with custom behavior
Zone zone = Zone.current.fork(
specification: ZoneSpecification(
// Intercept print operations
print: (Zone self, ZoneDelegate parent, Zone zone, String message) {
parent.print(zone, '[ZONE] $message');
},
),
);
// Run code inside the zone
zone.run(() {
print('Inside zone');
print('Another message');
});
print('Back outside');
}
// Output:
// Outside zone
// [ZONE] Inside zone
// [ZONE] Another message
// Back outside
What's happening here? -
Zone.currentgets the current zone -fork()creates a new zone with custom behavior -ZoneSpecificationdefines custom behavior -print()is intercepted and modified -run()executes code within the zone
Zone with Multiple Interceptions
Zones can intercept multiple operations like print, scheduleMicrotask, and timers.
import 'dart:async';
void main() {
Zone zone = Zone.current.fork(
specification: ZoneSpecification(
// Intercept print
print: (Zone self, ZoneDelegate parent, Zone zone, String message) {
parent.print(zone, 'LOG: $message');
},
// Intercept microtasks
scheduleMicrotask: (
Zone self,
ZoneDelegate parent,
Zone zone,
void Function() task,
) {
parent.print(zone, 'Scheduling microtask');
parent.scheduleMicrotask(zone, () {
parent.print(zone, 'Running microtask');
task();
});
},
// Intercept timer creation
createTimer: (
Zone self,
ZoneDelegate parent,
Zone zone,
Duration duration,
void Function() f,
) {
parent.print(zone, 'Creating timer: $duration');
return parent.createTimer(zone, duration, () {
parent.print(zone, 'Timer fired');
f();
});
},
),
);
zone.run(() {
print('Hello');
scheduleMicrotask(() => print('Microtask'));
Timer(Duration(seconds: 1), () => print('Timer'));
});
}
// Output:
// LOG: Hello
// Scheduling microtask
// LOG: Running microtask
// LOG: Microtask
// Creating timer: 0:00:01.000000
// (1 second later)
// Timer fired
// LOG: Timer
Key insights: - Multiple operations can be intercepted - Microtasks are intercepted and logged - Timers are intercepted with logging - The original behavior is preserved via
parent- This is useful for debugging and monitoring
Error Handling
Zone Error Handling
Zones can handle uncaught errors globally, preventing crashes and providing centralized error handling.
import 'dart:async';
void main() {
Zone zone = Zone.current.fork(
specification: ZoneSpecification(
// Handle uncaught errors
handleUncaughtError: (
Zone self,
ZoneDelegate parent,
Zone zone,
Object error,
StackTrace stack,
) {
parent.print(zone, 'Caught error: $error');
parent.print(zone, 'Stack: $stack');
},
// Intercept print
print: (Zone self, ZoneDelegate parent, Zone zone, String message) {
parent.print(zone, '[ZONE] $message');
},
),
);
// Run code that throws an error
zone.run(() {
print('Starting');
throw Exception('Something went wrong!');
print('This never executes');
});
print('App continues running');
}
// Output:
// [ZONE] Starting
// [ZONE] Caught error: Exception: Something went wrong!
// [ZONE] Stack: ...
// App continues running
What's happening here? -
handleUncaughtErrorcatches any uncaught errors - The error doesn't crash the app - You can log, report, or handle the error - The app continues running - This is perfect for global error handling
Custom Error Handling
Zones can implement complex error handling strategies.
import 'dart:async';
class ErrorHandler {
final List<Object> errors = [];
void handleError(Object error, StackTrace stack) {
errors.add(error);
print('Error logged: $error');
}
}
void main() {
var handler = ErrorHandler();
Zone zone = Zone.current.fork(
specification: ZoneSpecification(
handleUncaughtError: (
Zone self,
ZoneDelegate parent,
Zone zone,
Object error,
StackTrace stack,
) {
// Log the error
handler.handleError(error, stack);
// Re-throw in some cases
if (error is Exception && error.toString().contains('fatal')) {
// Let it crash for fatal errors
parent.handleUncaughtError(zone, error, stack);
}
},
),
);
zone.run(() {
// Non-fatal error
throw Exception('Network timeout');
});
zone.run(() {
// Fatal error
throw Exception('fatal: Out of memory');
});
print('Total errors: ${handler.errors.length}');
}
// Output:
// Error logged: Exception: Network timeout
// Total errors: 1
// (Then fatal error crashes)
Key insights: - You can implement custom error logging - Decide which errors to handle vs. propagate - Track errors across the application - This is great for error reporting services
Zone Local Values
Storing Context
Zones can store local values that are accessible within the zone, useful for context like request IDs, user sessions, or configuration.
import 'dart:async';
void main() {
// Create zone with local value
Zone zone = Zone.current.fork(
zoneValues: {
'requestId': 'REQ-12345',
'userId': 'USER-42',
'isDebug': true,
},
);
zone.run(() {
// Access zone values
var requestId = Zone.current['requestId'] as String;
var userId = Zone.current['userId'] as String;
var isDebug = Zone.current['isDebug'] as bool;
print('Request: $requestId');
print('User: $userId');
print('Debug: $isDebug');
// Nested zone inherits values
var nestedZone = Zone.current.fork(
zoneValues: {
'requestId': 'REQ-67890', // Override
},
);
nestedZone.run(() {
var requestId = Zone.current['requestId'] as String;
var userId = Zone.current['userId'] as String;
print('Nested request: $requestId');
print('Nested user: $userId');
});
});
}
// Output:
// Request: REQ-12345
// User: USER-42
// Debug: true
// Nested request: REQ-67890
// Nested user: USER-42
What's happening here? -
zoneValuesstores context data - Values are accessible withZone.current['key']- Nested zones inherit values - Values can be overridden in child zones - This is great for request contexts
Real-World Examples
HTTP Request Context
Zones are perfect for managing HTTP request contexts with logging and error handling.
import 'dart:async';
class RequestContext {
final String requestId;
final String userId;
final DateTime timestamp;
RequestContext(this.requestId, this.userId, this.timestamp);
@override
String toString() => '$requestId (User: $userId)';
}
class ApiService {
Future<void> handleRequest(String userId) async {
// Create a zone with request context
var zone = Zone.current.fork(
zoneValues: {
'context': RequestContext(
'REQ-${DateTime.now().millisecondsSinceEpoch}',
userId,
DateTime.now(),
),
},
specification: ZoneSpecification(
handleUncaughtError: (
Zone self,
ZoneDelegate parent,
Zone zone,
Object error,
StackTrace stack,
) {
// Get context for error logging
var context = zone['context'] as RequestContext;
print('Error in request ${context.requestId}: $error');
print('User: ${context.userId}');
// Don't rethrow - handle gracefully
},
print: (Zone self, ZoneDelegate parent, Zone zone, String message) {
var context = zone['context'] as RequestContext?;
if (context != null) {
parent.print(zone, '[${context.requestId}] $message');
} else {
parent.print(zone, message);
}
},
),
);
// Process request in the zone
await zone.run(() => _processRequest(userId));
}
Future<void> _processRequest(String userId) async {
var context = Zone.current['context'] as RequestContext;
print('Processing request: $context');
// Simulate work
await Future.delayed(Duration(seconds: 1));
// Simulate potential error
if (userId == 'error') {
throw Exception('Invalid user');
}
print('Request completed successfully');
}
}
void main() async {
var api = ApiService();
print('=== Successful request ===');
await api.handleRequest('user123');
print('\n=== Failed request ===');
await api.handleRequest('error');
print('\n=== Done ===');
}
// Output:
// === Successful request ===
// [REQ-1234567890] Processing request: REQ-1234567890 (User: user123)
// [REQ-1234567890] Request completed successfully
//
// === Failed request ===
// [REQ-1234567891] Processing request: REQ-1234567891 (User: error)
// Error in request REQ-1234567891: Exception: Invalid user
// User: error
//
// === Done ===
Performance Monitoring
Zones can be used to monitor and measure performance of code execution.
import 'dart:async';
class PerformanceMonitor {
static final Map<String, List<Duration>> _measurements = {};
static void recordMeasurement(String name, Duration duration) {
_measurements.putIfAbsent(name, () => []).add(duration);
print('⏱ $name took ${duration.inMilliseconds}ms');
}
static void printStats() {
print('\n=== Performance Stats ===');
for (var entry in _measurements.entries) {
var durations = entry.value;
var avg = durations.reduce((a, b) => a + b) ~/ durations.length;
print('${entry.key}:');
print(' Count: ${durations.length}');
print(' Average: ${avg.inMilliseconds}ms');
print(' Min: ${durations.reduce((a, b) => a < b ? a : b).inMilliseconds}ms');
print(' Max: ${durations.reduce((a, b) => a > b ? a : b).inMilliseconds}ms');
}
}
}
void main() {
// Create zone with performance monitoring
var zone = Zone.current.fork(
specification: ZoneSpecification(
// Monitor timers
createTimer: (
Zone self,
ZoneDelegate parent,
Zone zone,
Duration duration,
void Function() f,
) {
var start = DateTime.now();
return parent.createTimer(zone, duration, () {
var end = DateTime.now();
var elapsed = end.difference(start);
PerformanceMonitor.recordMeasurement('Timer', elapsed);
f();
});
},
// Monitor microtasks
scheduleMicrotask: (
Zone self,
ZoneDelegate parent,
Zone zone,
void Function() task,
) {
var start = DateTime.now();
parent.scheduleMicrotask(zone, () {
var end = DateTime.now();
var elapsed = end.difference(start);
PerformanceMonitor.recordMeasurement('Microtask', elapsed);
task();
});
},
),
);
zone.run(() {
print('Starting performance tests...');
// Several operations
for (var i = 0; i < 5; i++) {
Timer(Duration(milliseconds: 100 * (i + 1)), () {
print('Timer $i done');
});
}
scheduleMicrotask(() => print('Microtask 1'));
scheduleMicrotask(() => print('Microtask 2'));
});
// Wait for all operations
Future.delayed(Duration(seconds: 3), () {
PerformanceMonitor.printStats();
});
}
Best Practices
Use Zones for Cross-Cutting Concerns
// Good: Zones for logging
void setupLogging() {
Zone.current.fork(
specification: ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String msg) {
parent.print(zone, '[${DateTime.now()}] $msg');
},
),
).run(() {
print('This will be logged with timestamp');
});
}
Use Zones for Error Boundaries
// Good: Error boundary with zone
void withErrorBoundary(Function fn) {
Zone.current.fork(
specification: ZoneSpecification(
handleUncaughtError: (zone, parent, error, stack) {
// Handle error gracefully
print('Error captured: $error');
// Report to error service
},
),
).run(fn);
}
Common Mistakes
Modifying Zone in Existing Code
Wrong:
void someFunction() {
print('Hello'); // Can't modify this without zone
}
Correct:
void someFunction() {
Zone.current.fork(
specification: ZoneSpecification(
print: (zone, parent, msg) => parent.print(zone, 'MODIFIED: $msg'),
),
).run(() {
print('Hello'); // This will be modified
});
}
Forgetting to Call Parent
Wrong:
Zone.current.fork(
specification: ZoneSpecification(
print: (zone, parent, msg) {
// Forgot to call parent.print
// The original print won't execute
},
),
);
Correct:
Zone.current.fork(
specification: ZoneSpecification(
print: (zone, parent, msg) {
// Call parent.print to execute original behavior
parent.print(zone, msg);
// Add custom behavior
},
),
);
Summary
Zones provide powerful execution context management, enabling error handling, logging, monitoring, and context propagation across asynchronous operations.
Next Steps
Now that you understand zones, continue to:
Did You Know?
- Zones were introduced in Dart 1.0
- Each isolate has its own zone hierarchy
- Zones can be nested
- Zone values are inherited
- Zones can intercept most async operations
- Zones are used in Flutter for error handling
- The root zone is created by the Dart runtime