No history yet

Objects and Classes

Blueprints for Your Code

So far, you’ve learned to work with individual pieces of data like strings and numbers, and group them in lists. But what if you need to represent something more complex, like a user? A user has a name (a String), an age (an Int), and an email (a String). Grouping these together every time would be a hassle.

This is where classes come in. A class is a blueprint for creating objects. Think of it like a cookie cutter. The cutter itself isn't a cookie, but it defines the shape and structure of every cookie you make with it. In (OOP), a class is the cookie cutter, and an object is the cookie.

In Dart, almost everything is an object, from the simplest number to the most complex screen layout in a Flutter app. Each object is an instance of a class. Let's create a blueprint for a user.

// This is a class declaration. It's a blueprint.
class User {
  // These are the properties. They're variables that belong to the class.
  String name = 'Anonymous';
  String email = '';

  // This is a method. It's a function that belongs to the class.
  void sayHello() {
    print('Hello, my name is $name.');
  }
}

This User class defines a blueprint with two properties (name and email) and one method (sayHello). It doesn't represent a specific user yet. It's just the template.

To create an actual user object (an instance), you call the class name as if it were a function. Then you can use the dot notation (.) to access its properties and methods.

void main() {
  // Create an instance (an object) of the User class.
  var userOne = User();

  // Access and change a property.
  userOne.name = 'Alice';
  userOne.email = 'alice@example.com';

  // Call a method on the object.
  userOne.sayHello(); // Prints: Hello, my name is Alice.

  var userTwo = User();
  userTwo.name = 'Bob';
  userTwo.sayHello(); // Prints: Hello, my name is Bob.
}

Creating Objects with Constructors

Setting each property one-by-one works, but it’s clumsy. It would be better if we could provide the initial values right when we create the object. We can do this with a special method called a s. A constructor's job is to build, or "construct," a new object.

A constructor has the same name as the class and is where you can set up the object's initial state.

Let’s add a constructor to our User class. To avoid confusion between the constructor's parameters (name, email) and the class's properties (name, email), we use the this keyword. this.name refers to the property belonging to the object instance, while name refers to the parameter passed into the constructor.

class User {
  String name;
  String email;

  // This is a constructor.
  User(String name, String email) {
    this.name = name;
    this.email = email;
  }

  void sayHello() {
    print('Hello, my name is $name.');
  }
}

void main() {
  // Now we create the user and provide the data at the same time.
  var userOne = User('Alice', 'alice@example.com');
  userOne.sayHello(); // Prints: Hello, my name is Alice.
}

Dart offers a handy shortcut for this exact pattern. The line User(this.name, this.email); does the same thing as the constructor above, but with less code.

Building on Existing Blueprints

What if we wanted a special kind of user, like a PremiumUser, who has all the properties of a regular User plus some extra features? We could copy all the code from User into a new PremiumUser class, but that's inefficient. If we update User, we'd have to update PremiumUser too.

Instead, we can use inheritance. Inheritance allows a new class to take on the properties and methods of an existing class. The new class (PremiumUser) is called a subclass, and the existing class (User) is the superclass. We use the extends keyword to define this relationship.

class User {
  String name;
  String email;

  User(this.name, this.email);

  void sayHello() {
    print('Hello, my name is $name.');
  }
}

// PremiumUser inherits from User.
class PremiumUser extends User {
  String membershipLevel;

  // The constructor needs to initialize the User part of the object too.
  // We use super() to call the parent class's constructor.
  PremiumUser(String name, String email, this.membershipLevel)
      : super(name, email);
}

void main() {
  var premiumMember = PremiumUser('Carol', 'carol@example.com', 'Gold');

  // We can access properties from both classes.
  print(premiumMember.name); // from User
  print(premiumMember.membershipLevel); // from PremiumUser

  // And we can call methods from the parent class.
  premiumMember.sayHello(); // from User. Prints: Hello, my name is Carol.
}

By extending User, our PremiumUser automatically gets the name, email, and sayHello members without us having to write them again. We just added the new membershipLevel property. This makes our code more organized and easier to maintain.

Quiz Questions 1/6

In Object-Oriented Programming, what is the primary role of a class?

Quiz Questions 2/6

Using the analogy from the text, if a class is a "cookie cutter," what is an "object"?

You now have the final foundational piece of Dart! Understanding classes, objects, and inheritance is crucial, as you'll use these concepts constantly when building user interfaces with Flutter.