Closure
The () => creates an anonymous function (also called a closure or lambda) that defers execution.
expect(
AppRoute('/test/:id', child: (context) => Container(), parameters: ['id']),
throwsAssertionError,
);
What happens:
AppRoute(...)constructor is called immediately- If there’s an assertion error, it’s thrown before
expect()even runs - The test crashes with an unhandled exception
expect()never gets a chance to catch and verify the assertion
expect(
() => AppRoute('/test/:id', child: (context) => Container(), parameters: ['id']),
throwsAssertionError,
);
What happens:
() => AppRoute(...)creates a function that contains the constructor call- This function is passed to
expect()without being executed expect()internally calls this function in a try-catch block- When the assertion error is thrown,
expect()catches it and verifies it matchesthrowsAssertionError
// Without () =>
AppRoute(...) // 💥 AssertionError thrown here!
expect(crashedAlready, throwsAssertionError); // Never reached
// With () =>
Function myFunction = () => AppRoute(...); // Function created, not executed
expect(myFunction, throwsAssertionError); // expect() calls myFunction safely
This pattern is called:
- Function/Closure in Dart
- Lambda expression in general programming
- Anonymous function
- Deferred execution pattern in testing contexts
// Testing any exception-throwing code
expect(() => throw Exception('test'), throwsException);
expect(() => 1 ~/ 0, throwsA(isA<IntegerDivisionByZeroException>()));
expect(() => myList[999], throwsRangeError);
// The pattern works for any code that might throw
expect(() => someMethodThatThrows(), throwsArgumentError);
Always use () => when testing code that should throw exceptions - it wraps the potentially-throwing code in a function so that expect() can safely execute and catch the exception to verify it’s the expected type.
// Button onPressed - deferred execution until user taps
ElevatedButton(
onPressed: () => print('Button pressed!'),
child: Text('Click Me'),
)
// Custom callback with parameters
void processData(String data, void Function(String result) onComplete) {
// Do some processing...
onComplete('Processed: $data');
}
// Usage
processData('my data', (result) => print(result));
// Riverpod providers - lazy initialization
final userProvider = Provider<User>((ref) {
// This closure only runs when the provider is first accessed
return User.fromPreferences();
});
// Stream transformations
stream
.where((item) => item.isValid) // Closure for filtering
.map((item) => item.name.toUpperCase()) // Closure for transformation
.listen((name) => print('User: $name')); // Closure for side effects
// Widget builders in Flutter
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) { // Closure - deferred widget creation
return ListTile(title: Text(items[index]));
},
)
// Factory functions
Map<String, Widget Function()> screenFactories = {
'/home': () => HomeScreen(), // Closures defer widget creation
'/profile': () => ProfileScreen(), // until navigation occurs
'/settings': () => SettingsScreen(),
};
// Collection operations
final numbers = [1, 2, 3, 4, 5];
final doubled = numbers.map((n) => n * 2); // [2, 4, 6, 8, 10]
final evens = numbers.where((n) => n % 2 == 0); // [2, 4]
final sum = numbers.reduce((a, b) => a + b); // 15
// Complex transformations
final users = await fetchUsers();
final activeUserNames = users
.where((user) => user.isActive)
.map((user) => user.name.toUpperCase())
.toList();
// Search field with debouncing
Timer? _debounceTimer;
void onSearchChanged(String query) {
_debounceTimer?.cancel();
_debounceTimer = Timer(Duration(milliseconds: 300), () {
// This closure runs after 300ms of no typing
performSearch(query);
});
}
// Lazy-loaded expensive computation
late final String _expensiveValue = () {
print('Computing expensive value...');
return performExpensiveCalculation();
}(); // Immediately invoked function expression (IIFE)
// Memoization pattern
final Map<String, String> _cache = {};
String getProcessedData(String input) {
return _cache.putIfAbsent(input, () {
// Closure only runs if cache miss
return expensiveProcessing(input);
});
}
// Animation listener
animationController.addListener(() {
// Closure runs on every animation frame
setState(() {
opacity = animationController.value;
});
});
// Future chaining
fetchUserData()
.then((user) => fetchUserPosts(user.id)) // Closure for chaining
.then((posts) => updateUI(posts)) // Another closure
.catchError((error) => showError(error)); // Error handling closure
// Building a simple query DSL
class QueryBuilder {
List<bool Function(User)> _filters = [];
QueryBuilder where(bool Function(User) predicate) {
_filters.add(predicate);
return this;
}
List<User> execute(List<User> users) {
return users.where((user) {
return _filters.every((filter) => filter(user));
}).toList();
}
}
// Usage
final activeAdults = QueryBuilder()
.where((user) => user.isActive) // Closure for filtering
.where((user) => user.age >= 18) // Another closure
.execute(allUsers);
// Retry with exponential backoff
Future<T> retryWithBackoff<T>(
Future<T> Function() operation, // Closure containing the operation
int maxAttempts,
) async {
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await operation(); // Execute the closure
} catch (e) {
if (attempt == maxAttempts) rethrow;
await Future.delayed(Duration(seconds: attempt * 2));
}
}
throw StateError('Should never reach here');
}
// Usage
final data = await retryWithBackoff(
() => apiClient.fetchData(), // Closure wraps the API call
3,
);
// Only compute if needed
String getUserDisplayName(User? user) {
return user?.name ?? (() {
// This closure only runs if user.name is null
print('Generating default name...');
return 'Anonymous User';
})();
}
// Conditional widget building
Widget buildConditionalWidget(bool condition) {
return condition
? ExpensiveWidget()
: Builder(builder: (context) {
// This closure only runs when condition is false
return FallbackWidget();
});
}
- Deferred Execution - Code runs only when needed
- Encapsulation - Capture variables from surrounding scope
- Reusability - Pass behavior as parameters
- Composability - Chain operations together
- Memory Efficiency - Lazy evaluation saves resources