Dart Fundamentals: The Language Behind Flutter · Dart Basics: Variables, Null Safety, Functions and Collections
Functions: Named Parameters, Arrows and Closures
Dart functions are objects: pass them around, return them, and create closures. Use positional, optional and named parameters (with required and defaults), and the => arrow syntax.
What you'll build
A price-formatting toolkit: a function with named and default parameters, arrow functions, a function that returns a configured function (a closure), and higher-order functions over a list.
The idea
- Parameters:
- positional:
int add(int a, int b) - optional positional:
String greet(String name, [String? title]) - named (very common in Flutter):
void send({required String to, String subject = 'Hello'}), called assend(to: '[email protected]').
- positional:
- Arrow syntax:
int square(int x) => x * x;for single expressions. - Functions are objects: assign them to variables, pass them to other functions (
list.map(format)), and return them. - Closures capture variables from where they were created, like a counter that remembers its count.
- Anonymous functions:
(x) => x * 2or(x) { return x * 2; }.
The code
String formatPrice(num amount, {String currency = '\$', int decimals = 2, bool showPlus = false}) {
final sign = showPlus && amount > 0 ? '+' : '';
return '$sign$currency${amount.toStringAsFixed(decimals)}';
}
double withTax(double amount, [double rate = 0.2]) => amount * (1 + rate);
String Function(num) formatterFor(String currency) {
return (num amount) => formatPrice(amount, currency: currency); // captures `currency`
}
int Function() makeCounter() {
var count = 0;
return () => ++count; // the closure keeps its own `count`
}
void main() {
print(formatPrice(12.5));
print(formatPrice(12.5, currency: '€', decimals: 0));
print(formatPrice(3, showPlus: true));
print('with tax: ${withTax(100).toStringAsFixed(2)} / ${withTax(100, 0.05).toStringAsFixed(2)}');
final inYen = formatterFor('¥');
final prices = [980, 1500, 12000];
print(prices.map(inYen).join(', '));
final cheap = prices.where((p) => p < 2000).toList();
final doubled = prices.map((p) => p * 2);
print('cheap: $cheap, doubled: ${doubled.toList()}');
final nextTicket = makeCounter();
final otherCounter = makeCounter();
print('tickets: ${nextTicket()}, ${nextTicket()}, ${nextTicket()}; other: ${otherCounter()}');
}
Output
$12.50 €13 +$3.00 with tax: 120.00 / 105.00 ¥980.00, ¥1500.00, ¥12000.00 cheap: [980, 1500], doubled: [1960, 3000, 24000] tickets: 1, 2, 3; other: 1
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Long lists of positional parameters. Use named parameters for clarity, especially booleans (
send(to: x, urgent: true)reads better thansend(x, true)). - Forgetting
requiredon named parameters that must be provided (non-nullable named parameters need a default orrequired). mapis lazy.prices.map(...)returns anIterable; call.toList()when you need a list.- Arrow functions with statements.
=>takes one expression; use braces for multiple statements.
Key points
- Positional, optional
[ ]and named{ }parameters;requiredand defaults. =>for one-expression functions.- Functions are values: pass, return and store them.
- Closures capture their surrounding variables.
Further reading: Functions.
Practice questions
How do named parameters work in Dart?
Answer
They're declared in braces, e.g. void send({required String to, String subject = 'Hi'}), and passed by name: send(to: '[email protected]'). They're optional unless marked required or given a non-null type without a default (which requires required).
What is a closure?
Answer
A function that captures variables from the scope where it was created and keeps access to them after that scope ends, e.g. a counter function that keeps incrementing its own captured count.
Open in the app to mark lessons complete, take notes and track your progress.