6 Functions
Functions are essential building blocks in Dart, just like in other programming languages. Given your experience with Python, you’ll find many similarities but also some important differences in how Dart handles functions. Let’s explore the fundamentals of Dart functions.
6.1 Basic Function Declaration
In Dart, you declare functions by specifying a return type, function name, parameters, and a body:
{
ReturnType functionName(ParameterType parameter) // Function body
return value;
}
Here’s a simple example of a function that adds two numbers:
int add(int a, int b) {
return a + b;
}
void main() {
5, 3)); // Output: 8
print(add(}
Notice how unlike Python, Dart requires: - Type declarations for parameters and return values - Curly braces {}
to define the function body - Semicolons ;
at the end of statements - The return
keyword to specify what value the function returns
6.2 Void Functions
If a function doesn’t return a value, you can use the void
keyword:
void printGreeting(String name) {
'Hello, $name!');
print(}
void main() {
'Kittipos'); // Output: Hello, Kittipos!
printGreeting(}
6.3 Function Parameters
Dart offers several ways to work with parameters:
6.3.1 Required Parameters
These are the standard parameters that must be provided when calling a function:
String createFullName(String firstName, String lastName) {
return '$firstName $lastName';
}
void main() {
'John', 'Doe')); // Output: John Doe
print(createFullName(}
6.3.2 Optional Positional Parameters
You can make parameters optional by wrapping them in square brackets []
:
String createFullName(String firstName, [String? lastName]) {
if (lastName != null) {
return '$firstName $lastName';
}
return firstName;
}
void main() {
'John', 'Doe')); // Output: John Doe
print(createFullName('John')); // Output: John
print(createFullName(}
Notice the use of String?
which is part of Dart’s null safety feature, indicating that lastName
can be null.
6.3.3 Optional Named Parameters
Named parameters make function calls more readable. Wrap them in curly braces {}
:
String createFullName(String firstName, {String? lastName}) {
if (lastName != null) {
return '$firstName $lastName';
}
return firstName;
}
void main() {
'John', lastName: 'Doe')); // Output: John Doe
print(createFullName('John')); // Output: John
print(createFullName(}
6.3.4 Default Parameter Values
You can provide default values for optional parameters:
String createFullName(String firstName, {String lastName = 'Unknown'}) {
return '$firstName $lastName';
}
void main() {
'John')); // Output: John Unknown
print(createFullName('John', lastName: 'Doe')); // Output: John Doe
print(createFullName(}
6.3.5 Required Named Parameters
In Dart, you can also make named parameters required using the required
keyword:
String createFullName({required String firstName, required String lastName}) {
return '$firstName $lastName';
}
void main() {
: 'John', lastName: 'Doe')); // Output: John Doe
print(createFullName(firstName// print(createFullName(firstName: 'John')); // Error: Missing required parameter 'lastName'
}
6.4 Arrow Functions (Lambda Expressions)
For simple functions that just return an expression, you can use the arrow syntax (=>
), similar to lambda functions in Python:
// Regular function
int add(int a, int b) {
return a + b;
}
// Arrow function equivalent
int addArrow(int a, int b) => a + b;
void main() {
5, 3)); // Output: 8
print(add(5, 3)); // Output: 8
print(addArrow(}
6.5 Anonymous Functions
Dart supports anonymous functions (functions without names), which are useful for one-time use or as arguments to other functions:
void main() {
var numbers = [1, 2, 3, 4, 5];
// Using an anonymous function with forEach
.forEach((number) {
numbers* 2);
print(number });
// Using an arrow function
.forEach((number) => print(number * 3));
numbers
// Even shorter with tear-off
.forEach(print);
numbers}
6.6 Higher-Order Functions
Dart supports higher-order functions, which are functions that take other functions as parameters or return functions:
Function makeMultiplier(int multiplier) {
// This function returns another function
return (int value) => value * multiplier;
}
void main() {
var doubler = makeMultiplier(2);
var tripler = makeMultiplier(3);
5)); // Output: 10
print(doubler(5)); // Output: 15
print(tripler(}
6.7 Closures
Dart functions are closures, meaning they can access variables from their outer scope:
void main() {
var message = 'Hello';
void printMessage() {
// This function can access the 'message' variable
'$message from inner function');
print(}
// Output: Hello from inner function
printMessage(); }
This is similar to how closures work in Python.
6.8 Recursive Functions
Dart supports recursive functions (functions that call themselves):
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
void main() {
5)); // Output: 120 (5! = 5*4*3*2*1)
print(factorial(}
6.9 Generator Functions
Dart has special functions called generators that can produce sequences of values:
// Synchronous generator (returns Iterable)
Iterable<int> countUpTo(int n) sync* {
for (int i = 1; i <= n; i++) {
yield i;
}
}
void main() {
for (var i in countUpTo(5)) {
// Output: 1, 2, 3, 4, 5
print(i); }
}
The sync*
and yield
keywords are used for creating generator functions.
6.10 Functions in Flutter Context
Let’s see how functions are used in a real Flutter widget:
class CounterWidget extends StatefulWidget {
@override
=> _CounterWidgetState();
_CounterWidgetState createState() }
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
// Event handler function
void _incrementCounter() {
{
setState(() ++;
_counter});
}
// Helper function with named parameters
{required int value, Color color = Colors.black}) {
Widget _buildCounterText(return Text(
'Count: $value',
: TextStyle(fontSize: 24, color: color),
style
);}
@override
{
Widget build(BuildContext context) return Scaffold(
: AppBar(title: Text('Counter')),
appBar: Center(
body: _buildCounterText(value: _counter, color: Colors.blue),
child,
): FloatingActionButton(
floatingActionButton: _incrementCounter, // Passing function reference
onPressed: Icon(Icons.add),
child,
)
);}
}
In this example, we use: - An event handler function (_incrementCounter
) - A helper function with named parameters (_buildCounterText
) - Function references (onPressed: _incrementCounter
)
6.11 Comparing with Python
Let’s compare Dart functions with Python:
# Python function
def add(a, b):
return a + b
# Python function with default parameter
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# Python lambda function
= lambda a, b: a * b multiply
// Dart function
int add(int a, int b) {
return a + b;
}
// Dart function with default parameter
String greet(String name, {String greeting = "Hello"}) {
return "$greeting, $name!";
}
// Dart arrow function
int multiply(int a, int b) => a * b;
The main differences are: 1. Dart uses static typing (type declarations for parameters and return values) 2. Dart uses named parameters with curly braces instead of Python’s keyword arguments 3. Dart requires semicolons at the end of statements 4. Dart has arrow functions instead of lambda expressions
6.12 Practical Exercise
Let’s create a small example that uses different types of functions:
void main() {
// Standard function
int multiply(int a, int b) {
return a * b;
}
// Arrow function with named parameters
double calculateArea({required double length, required double width}) => length * width;
// Higher-order function
void applyAndPrint(int value, int Function(int) transformer) {
var result = transformer(value);
'Transformed value: $result');
print(}
// Anonymous function
var square = (int x) => x * x;
'5 x 3 = ${multiply(5, 3)}');
print('Area: ${calculateArea(length: 10.5, width: 5.2)}');
print(4, square);
applyAndPrint(3, (x) => x * x * x); // Cube function inline
applyAndPrint(}
This exercise demonstrates:
- A standard function
- An arrow function with named parameters
- A higher-order function
- Anonymous functions