Dart: Abstract Classes

Dart is a programming language made by Google. It is used to build fast and good-looking apps for mobile phones, websites, and computers. If you’ve used Flutter, you’ve already used Dart.

In Dart, you work with classes to create objects. A class is like a recipe — it tells the computer how to make and use an object. For example, you might have a Dog class to create dog objects with names and sounds.

But sometimes, you don’t want a full recipe. You just want a guide — something that says what must be included, without doing all the work. This is where an abstract class comes in.

An abstract class:

  • Cannot be used to make objects directly.
  • Can have method names (with or without code).
  • Is used by other classes, which “follow the guide” and add their own code.

Think of it like a contract:

“If you use me, you must finish the parts I didn’t write.”

This helps keep your code clean and organized, especially when many classes need to share the same ideas but work differently.

Why Use Abstract Classes

In Dart, an abstract class helps you build a blueprint — a basic plan for how other classes should look and act.

Let’s say you are making a game with many animals: dogs, cats, lions, birds. All animals can make sounds, move, or sleep, but each one does it in a different way. Instead of writing the same method names again and again, you can make an abstract class called Animal that says:

“Every animal must have a makeSound() method.”

Now, each animal class must follow that pattern and write its own version of the method.

This is useful because:

  • Your code becomes easier to understand.
  • You don’t forget to add important methods.
  • Everything stays organized when the project grows.

It’s like setting rules. The abstract class makes sure that all animal classes play by those rules.

How to Write an Abstract Class

To write an abstract class in Dart, use the abstract keyword before the class name. This tells Dart:

“This class is only a guide. Don’t let anyone create an object from it.”

An abstract class can do two things:

  1. It can have normal methods: These are methods that have a body (some code inside). You can use them just like in any regular class.
  2. It can have methods without a body: These are like empty method names. They don’t do anything yet — they just say,

“Whoever uses this class must write this method.”

Here’s a simple example:

abstract class Animal {

  void makeSound(); // No body — must be written later

  void sleep() {
    print("Sleeping... Zzz"); // Normal method
  }

}

In this case, makeSound() has no body—it’s just a rule that child classes must follow. Meanwhile, sleep() has code inside, so any class using Animal will automatically have this method ready to use.

Code Example: Animal Sounds

Let’s create an abstract class called Animal. It will have one method with no body (makeSound) and one normal method (sleep).

abstract class Animal {

  void makeSound(); // No body. Must be written by other classes.

  void sleep() {
    print("Sleeping... Zzz"); // Normal method with body.
  }

}

In this example, makeSound() is a rule that every animal must follow, but each one decides how it sounds. The sleep() method is already complete, so any animal using this class can call it right away.

This is the base that other animal classes will build on.

Code Example: Dog and Cat

Now let’s create two classes — Dog and Cat. Both will extend the Animal abstract class and give their own version of the makeSound() method.

class Dog extends Animal {

  void makeSound() {
    print("Woof!");
  }

}

class Cat extends Animal {

  void makeSound() {
    print("Meow!");
  }

}

In this example, both Dog and Cat follow the Animal plan. Each class writes its own version of makeSound(), while they automatically get the sleep() method from Animal to use.

This shows how different animals can follow the same rules but act in their own way.

Using the Classes

Now let’s use the Dog and Cat classes in the main() function. We’ll see how they make their own sounds and also use the shared sleep() method.

void main() {

  Dog dog = Dog();
  Cat cat = Cat();

  dog.makeSound(); // Woof!
  cat.makeSound(); // Meow!

  dog.sleep();     // Sleeping... Zzz
  cat.sleep();     // Sleeping... Zzz

}

Here, we create one dog and one cat. Each animal makes its own sound by using its own version of makeSound(). Both animals also use the shared sleep() method from the Animal abstract class.

This shows how abstract classes let different objects follow the same pattern but behave in their own way.

Inheritance with Abstract Classes

In Dart, an abstract class can inherit from another class. This means it can get methods and variables from a parent class and then add its own rules or methods.

You are not limited to inheriting only complete (normal) classes; an abstract class can also inherit from another abstract class. This lets you build a chain of abstract classes, each adding new rules or shared code.

For example, if you have a basic Creature class, you can create an abstract class Animal that extends it, adding more details that child classes like Dog or Cat will follow.

class Creature {

  void breathe() {
    print("Breathing...");
  }
  
}

abstract class Animal extends Creature {
  void makeSound();
}

class Dog extends Animal {

  @override
  void makeSound() {
    print("Woof!");
  }
  
}

void main() {

  Dog dog = Dog();
  
  dog.breathe();    // Inherited from Creature
  dog.makeSound();  // Implemented from Animal
  
}

Animal inherits the breathe() method from Creature and adds an abstract method makeSound(), which Dog must implement when it extends Animal. This shows how abstract classes can build on other classes by adding new rules.

This shows how abstract classes can build on top of other classes—abstract or concrete—to create a chain of behaviors and rules.

Example: Abstract Class Inheriting Another Abstract Class

In this example, we have two abstract classes: LivingBeing and Animal. Animal inherits from LivingBeing and adds its own abstract method. Then, a concrete class Dog implements all required methods.

abstract class LivingBeing {
  void breathe();
}

abstract class Animal extends LivingBeing {
  void makeSound();
}

class Dog extends Animal {

  @override
  void breathe() {
    print("Dog is breathing...");
  }

  @override
  void makeSound() {
    print("Woof!");
  }
  
}

void main() {

  Dog dog = Dog();
  
  dog.breathe();    // From LivingBeing
  dog.makeSound();  // From Animal
  
}

LivingBeing sets a basic rule by declaring an abstract method breathe(). Building on that, Animal inherits from LivingBeing and adds its own rule with the abstract method makeSound(). Finally, Dog extends Animal and is responsible for providing the actual code for both breathe() and makeSound().

This example shows how abstract classes can inherit from other abstract classes to build layered contracts for your code.

Implementing Interfaces with Abstract Classes

In Dart, every class defines an interface automatically. An interface is like a list of methods a class promises to have. You can use an abstract class as an interface by implementing it in another class.

When a class implements an abstract class, it must write all the methods that the abstract class declares—even those with code in the abstract class.

This is different from extending, where a class can reuse code from the abstract class.

abstract class Vehicle {

  void start();
  void stop();

  void info() {
    print("This is a vehicle.");
  }

}

class Car implements Vehicle {

  @override
  void start() {
    print("Car is starting.");
  }

  @override
  void stop() {
    print("Car has stopped.");
  }

  @override
  void info() {
    print("This is a car.");
  }

}

void main() {

  Car car = Car();

  car.start();  // Car is starting.
  car.info();   // This is a car.
  car.stop();   // Car has stopped.

}

Vehicle is an abstract class that declares two abstract methods: start and stop. It also includes a normal method called info, which has some code inside.

When the class Car implements the Vehicle abstract class, it must provide its own version of all three methods, including info. This means Car has to write the code for start, stop, and also info, even though Vehicle already has code for info.

The reason is that implementing an abstract class means following its interface strictly. Car agrees to have all the methods listed in Vehicle but controls how each method works. This way, Car can behave differently while still matching the expected structure.

Abstract Class Cannot Be Used Directly

You cannot create an object from an abstract class. It is only a guide, not a full class. Dart will show an error if you try.

For example:

Animal a = Animal();

This line is not allowed because Animal is abstract. It’s missing parts — like makeSound() — so Dart does not know what to do.

However, you can assign an object of a complete class to a variable typed as the abstract class. For example:

Animal dog = Dog();
Animal cat = Cat();

Here, dog and cat are treated as Animal type but use the full class methods.

Real Code in Abstract Classes

In Dart, abstract classes can do more than just list method names. They can also include:

Methods with a body

These are normal methods in an abstract class that include code inside them. When other classes extend this abstract class, they automatically get these methods without needing to write them again.

This helps you share common behavior across many classes.

void sleep() {
  print("Sleeping... Zzz");
}

Here, sleep() is a method that prints a message. Any class that extends the abstract class will have this method ready to use.

Methods without a body (abstract methods)

These methods only have a name and a return type but no code inside. They act like a rule. Any class that extends the abstract class must write the code for these methods.

This helps ensure that all child classes follow the same pattern.

void makeSound();

Here, makeSound() is an abstract method. The abstract class says:

“Every class that uses me must create its own makeSound() method.”

If a child class does not write this method, Dart will show an error.

Variables

Abstract classes can also have variables (called fields). These store data that all child classes can use or change.

This lets you keep shared information in one place.

String name;

Here, name is a variable inside the abstract class. Any class that extends this abstract class can use name to hold its own value.

Here is a full example combining variables, abstract methods, and normal methods in one abstract class:

abstract class Animal {

  String name; // Variable to hold the animal's name

  Animal(this.name); // Constructor to set the name

  void makeSound(); // Abstract method - no body

  void sleep() {    // Normal method with body
    print("$name is sleeping... Zzz");
  }

}

class Dog extends Animal {

  Dog(String name) : super(name);

  @override
  void makeSound() {
    print("$name says: Woof!");
  }

}

class Cat extends Animal {

  Cat(String name) : super(name);

  @override
  void makeSound() {
    print("$name says: Meow!");
  }

}

void main() {

  Dog dog = Dog("Buddy");
  Cat cat = Cat("Whiskers");

  dog.makeSound(); // Buddy says: Woof!
  cat.makeSound(); // Whiskers says: Meow!

  dog.sleep();     // Buddy is sleeping... Zzz
  cat.sleep();     // Whiskers is sleeping... Zzz

}

The variable name holds the animal’s name for each object. The method makeSound() is abstract, so child classes must write their own version to define the animal’s sound. The method sleep() already has code, so all child classes get it automatically and can use it as is. Classes like Dog or Cat extend Animal, adding their own makeSound() but sharing the same name and sleep(). This way, shared parts stay in one place, and unique parts are added where needed, keeping your code simple and organized.

Code Example: Robot Animals

Here’s an example of an abstract class for robot animals. It uses variables, abstract methods, and normal methods, just like before.

abstract class RobotAnimal {

  String name;

  RobotAnimal(this.name); // Constructor to set the name

  void charge(); // Abstract method - must be implemented by subclasses

  void showName() { // Normal method with code
    print("I am $name");
  }

}

class RoboDog extends RobotAnimal {

  RoboDog(String name) : super(name);

  @override
  void charge() {
    print("$name is charging...");
  }

}

class RoboCat extends RobotAnimal {

  RoboCat(String name) : super(name);

  @override
  void charge() {
    print("$name is charging with laser power!");
  }

}

void main() {

  RoboDog roboDog = RoboDog("Bolt");
  RoboCat roboCat = RoboCat("Sparky");

  roboDog.showName();    // I am Bolt
  roboDog.charge();      // Bolt is charging...

  roboCat.showName();    // I am Sparky
  roboCat.charge();      // Sparky is charging with laser power!

}

RobotAnimal is abstract with a name, an abstract method charge(), and a normal method showName(). RoboDog and RoboCat extend RobotAnimal and provide their own charge() method. In main(), we create robot dog and cat objects, then call their methods to see the results.

Conclusion

Abstract classes help you build a clear structure for your code. They act like a plan that many classes can follow. This makes your code easier to organize and understand.

By using abstract classes, you ensure that many classes share the same method names. At the same time, each class can add its own details. This balance keeps your code clean and consistent.

Overall, abstract classes are a useful tool to keep your programming neat and easy to follow, especially when working with many related classes.