Dart: Functions & Classes

With the basics of variables and types out of the way, this lesson covers how Dart organizes behavior (functions) and behavior + data together (classes). Since every widget in Flutter is itself a class, and every widget's build() method is itself a function, this is genuinely foundational material for everything that follows.

Defining a Function

A Dart function declares a return type, a name, a parameter list in parentheses, and a body in curly braces.

int add(int a, int b) {
  return a + b;
}

void greet(String name) {
  print('Hello, $name!');
}

// calling them
int sum = add(2, 3);   // 5
greet('Alice');        // Hello, Alice!

void is used as the return type when a function doesn't return a value — you'll see it constantly on methods that perform an action, like setState callbacks.

Arrow Syntax for Single-Expression Functions

When a function's entire body is just one expression that gets returned, Dart lets you skip the curly braces and return keyword entirely using the arrow syntax (=>). This is extremely common in Flutter code, especially inside widget classes.

// Full form
int add(int a, int b) {
  return a + b;
}

// Equivalent arrow form -- identical behavior, less code
int add(int a, int b) => a + b;

bool isEven(int n) => n % 2 == 0;

Named Parameters

Dart supports named parameters — parameters that are passed by name at the call site rather than by position, wrapped in curly braces in the function signature. This is used constantly in Flutter, since widget constructors rely heavily on it for readability.

void greet({required String name, String greeting = 'Hello'}) {
  print('$greeting, $name!');
}

// called using name: value syntax
greet(name: 'Alice');                          // Hello, Alice!
greet(name: 'Bob', greeting: 'Hey');           // Hey, Bob!

Two things are happening in that signature: required means name must be provided every time — the compiler enforces it, so it's a compile error to omit it. greeting, on the other hand, has a default value of 'Hello', so it's optional — leave it out and Dart uses the default.

Why this matters for Flutter: Widget constructors are almost always called with named parameters, e.g. Text('Hi', style: TextStyle(fontSize: 20)) — this is exactly the mechanism you just learned.

Optional Positional Parameters

As a brief contrast, Dart also supports optional positional parameters using square brackets instead of curly braces. These are passed by position (not by name) and are simply omitted if not needed.

String fullName(String first, [String? middle, String? last]) {
  if (middle != null && last != null) return '$first $middle $last';
  if (last != null) return '$first $last';
  return first;
}

fullName('Alice');                    // Alice
fullName('Alice', 'Marie', 'Smith');   // Alice Marie Smith

In practice, named parameters are far more common in Flutter/Dart code because they make call sites self-documenting — you rarely see square-bracket optional parameters outside a handful of core library APIs.

Defining a Class

Classes bundle data (fields) and behavior (methods) together. Dart has a convenient shorthand constructor syntax — this.fieldName in the constructor's parameter list — that assigns an incoming parameter directly to a field of the same name, without writing a body at all.

class Person {
  String name;
  int age;

  // shorthand constructor: this.name / this.age assign the
  // incoming arguments straight to the fields -- no body needed
  Person(this.name, this.age);

  void introduce() {
    print('Hi, I\'m $name and I\'m $age years old.');
  }
}

Person alice = Person('Alice', 28);
alice.introduce();  // Hi, I'm Alice and I'm 28 years old.

You can also use named parameters directly in a constructor, which is exactly the pattern every widget class uses:

class Product {
  String title;
  double price;

  Product({required this.title, required this.price});
}

Product item = Product(title: 'Mug', price: 9.99);

Named Constructors

A class can have more than one constructor by giving alternate constructors a name after a dot. This is handy for expressing different, meaningful ways to create an instance.

class Person {
  String name;
  int age;

  Person(this.name, this.age);

  // A named constructor -- Person.guest()
  Person.guest() : name = 'Guest', age = 0;
}

Person visitor = Person.guest();
print(visitor.name);  // Guest

Inheritance with extends and super

A class can inherit fields and methods from another class using extends. The subclass's constructor can forward arguments to the parent class's constructor using super — this is exactly what every custom widget does when it extends StatelessWidget or StatefulWidget.

class Animal {
  String name;
  Animal(this.name);

  void speak() {
    print('$name makes a sound.');
  }
}

class Dog extends Animal {
  Dog(String name) : super(name);  // forwards name to Animal's constructor

  @override
  void speak() {
    print('$name barks.');
  }
}

Dog rex = Dog('Rex');
rex.speak();  // Rex barks.

The @override annotation above documents (and lets the compiler verify) that speak() is intentionally replacing the parent class's version.

Mixins with the with Keyword

Dart's mixins are a genuinely distinctive feature: a way to share a chunk of reusable code across classes that don't share a common parent, without traditional multiple inheritance. You define a mixin with the mixin keyword, and any class can pull it in with with.

mixin Flyer {
  void fly() => print('Flying!');
}

class Bird extends Animal with Flyer {
  Bird(String name) : super(name);
}

Bird sparrow = Bird('Sparrow');
sparrow.fly();  // Flying!

Bird gets fly() "for free" from the Flyer mixin, on top of whatever it inherits from Animal via extends. You'll encounter mixins again later in this track (for example, SingleTickerProviderStateMixin when working with animations) — for now, just recognize the pattern when you see with in a class declaration.

Next steps: You now know enough Dart to read real Flutter code comfortably. Head to Your First App to create a project and walk through Flutter's own generated starter app line by line.