8 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.
8.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() {
print(add(5, 3)); // Output: 8
}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
8.2 Void Functions
If a function doesn’t return a value, you can use the void keyword:
void printGreeting(String name) {
print('Hello, $name!');
}
void main() {
printGreeting('Kittipos'); // Output: Hello, Kittipos!
}8.3 Function Parameters
Dart offers several ways to work with parameters:
8.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() {
print(createFullName('John', 'Doe')); // Output: John Doe
}8.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() {
print(createFullName('John', 'Doe')); // Output: John Doe
print(createFullName('John')); // Output: John
}Notice the use of String? which is part of Dart’s null safety feature, indicating that lastName can be null.
8.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() {
print(createFullName('John', lastName: 'Doe')); // Output: John Doe
print(createFullName('John')); // Output: John
}8.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() {
print(createFullName('John')); // Output: John Unknown
print(createFullName('John', lastName: 'Doe')); // Output: John Doe
}8.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() {
print(createFullName(firstName: 'John', lastName: 'Doe')); // Output: John Doe
// print(createFullName(firstName: 'John')); // Error: Missing required parameter 'lastName'
}8.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() {
print(add(5, 3)); // Output: 8
print(addArrow(5, 3)); // Output: 8
}8.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
numbers.forEach((number) {
print(number * 2);
});
// Using an arrow function
numbers.forEach((number) => print(number * 3));
// Even shorter with tear-off
numbers.forEach(print);
}8.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);
print(doubler(5)); // Output: 10
print(tripler(5)); // Output: 15
}8.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
print('$message from inner function');
}
printMessage(); // Output: Hello from inner function
}This is similar to how closures work in Python.
8.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() {
print(factorial(5)); // Output: 120 (5! = 5*4*3*2*1)
}8.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)) {
print(i); // Output: 1, 2, 3, 4, 5
}
}The sync* and yield keywords are used for creating generator functions.
8.10 Functions in Flutter Context
Let’s see how functions are used in a real Flutter widget:
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
// Event handler function
void _incrementCounter() {
setState(() {
_counter++;
});
}
// Helper function with named parameters
Widget _buildCounterText({required int value, Color color = Colors.black}) {
return Text(
'Count: $value',
style: TextStyle(fontSize: 24, color: color),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: _buildCounterText(value: _counter, color: Colors.blue),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter, // Passing function reference
child: Icon(Icons.add),
),
);
}
}In this example, we use: - An event handler function (_incrementCounter) - A helper function with named parameters (_buildCounterText) - Function references (onPressed: _incrementCounter)
8.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
multiply = lambda a, b: a * b// 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
8.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);
print('Transformed value: $result');
}
// Anonymous function
var square = (int x) => x * x;
print('5 x 3 = ${multiply(5, 3)}');
print('Area: ${calculateArea(length: 10.5, width: 5.2)}');
applyAndPrint(4, square);
applyAndPrint(3, (x) => x * x * x); // Cube function inline
}This exercise demonstrates:
- A standard function
- An arrow function with named parameters
- A higher-order function
- Anonymous functions