Skip to main content
An abstract method is a function declaration that defines a signature—comprising the name, return type, and parameter list—without providing an implementation body. It establishes a mandatory interface contract that concrete implementations must satisfy. Syntactically, the declaration terminates immediately with a semicolon ; rather than a code block enclosed in braces {}.

Declaration Context

Abstract methods are explicitly declared within two specific constructs:
  1. Abstract Classes: Classes marked with the abstract modifier.
  2. Mixins: Reusable class code structures declared with the mixin keyword.
Note: Enhanced Enums in Dart are implicitly concrete and final; they cannot declare abstract methods.
// 1. Abstract Class
abstract class DataProvider {
  void connect(String uri); // Abstract method declaration
}

// 2. Mixin
mixin Logger {
  void log(String message); // Abstract method declaration
}

Implementation Requirements

When a concrete (non-abstract) class inherits from an abstract class, applies a mixin, or implements an interface, it must provide a concrete implementation for every abstract member defined in the hierarchy. The @override annotation is used to flag the implementation for the compiler, ensuring the signature matches the abstract definition.
class Database extends DataProvider with Logger {
  // Implementation of abstract method from DataProvider
  @override
  void connect(String uri) {
    print('Connecting to $uri');
  }

  // Implementation of abstract method from Logger mixin
  @override
  void log(String message) {
    print('Log: $message');
  }
}

Type Constraints and Variance

Dart enforces sound type safety when overriding methods. The implementing method must adhere to specific variance rules regarding return types and parameters:
  1. Return Type (Covariance): The implementing method must return the same type as the abstract method or a subtype of that type.
  2. Parameter Types (Contravariance): The implementing method must accept the same parameter types as the abstract method or supertypes of those types.
  3. Parameter Structure (Arity and Names):
    • Required Parameters: The implementing method must accept all required positional and named parameters defined in the abstract signature.
    • Optional Parameters: The implementing method may declare additional parameters (positional or named) provided they are optional. This ensures the method remains compatible with callers using the original abstract signature.
class Animal {}
class Dog extends Animal {}

abstract class Handler {
  // Abstract signature
  Dog getAnimal(Animal input);
}

class DogHandler extends Handler {
  @override
  // Return Type: Dog (Match) or Subtype (allowed)
  // Parameter Type: Object (Supertype of Animal - allowed)
  // Optional Parameters: Added named and positional optionals allowed
  Dog getAnimal(Object input, [int? index, String? tag]) { 
    return Dog();
  }
}

Inheritance and Propagation

The obligation to implement abstract methods depends on the nature of the subclass:
  1. Abstract Subclasses: If a subclass is declared abstract, it inherits the abstract signatures but is not required to implement them. It may also declare new abstract methods.
  2. Concrete Subclasses: The obligation to provide implementations propagates down the inheritance chain to the first concrete descendant. This class must implement all accumulated abstract methods from its ancestors.
abstract class NetworkProvider extends DataProvider {
  // Inherits 'connect' from DataProvider.
  // No implementation required here.
  
  // Declares new abstract method.
  void disconnect();
}

class SecureSocket extends NetworkProvider {
  // Must implement methods from both DataProvider and NetworkProvider
  @override
  void connect(String uri) { /*...*/ }

  @override
  void disconnect() { /*...*/ }
}

Implicit Interfaces

Every class in Dart implicitly defines an interface containing all its instance members. When using the implements keyword, the target class’s members are treated as abstract signatures, regardless of whether the original class was abstract or concrete.
  • Concrete Implementers: If the implementing class is concrete, it must override and implement all members of the interface.
  • Abstract Implementers: If the implementing class is abstract, it inherits the method signatures as abstract methods. It is not required to provide a body immediately; the requirement is passed to concrete subclasses.
class ConcreteService {
  void run() { print('Running'); }
}

// Abstract class implementing an interface
abstract class BaseJob implements ConcreteService {
  // 'run' is inherited as an abstract method here.
  // BaseJob is NOT required to implement it.
}

class FinalJob extends BaseJob {
  // Concrete subclass MUST implement 'run'
  @override
  void run() { 
    print('Custom Run'); 
  }
}
Master Dart with Deep Grasping Methodology!Learn More