İçeriğe geç / Skip to content / Zum Inhalt

Dart Interfaces and implements: Contracts for Classes

Ahmet Balaman

Last updated:

8 min read

FlutterDartInterfaceAbstractOOP
Dart Interfaces and implements: Contracts for Classes

In the post on inheritance and polymorphism we saw that with extends a class derives from exactly one superclass. Real objects, however, often have several abilities: an apple can be eaten and squeezed, and a payment service should keep a log while also being replaceable by a fake during tests. Dart offers three separate tools for these needs: abstract classes, interfaces and mixins.

The point learners confuse most is assuming these three do the same job. Let's separate them briefly:

  • Abstract class: Partial implementation and contract together. It sets up an "is-a" relationship.
  • Interface: Contract only. It says "can do this," and the implementing class writes the code.
  • Mixin: A ready-made package of behavior. It hands you code but doesn't create an "is-a" relationship.

This post covers all three, together with Dart 3's class modifiers.

Live Demo: Abstract Class and Implements

Learn abstract class and implements usage interactively:

In Dart, Every Class Is an Interface

People coming from Java or C# are used to declaring a separate construct with the interface keyword. In Dart, every class implicitly defines its own interface as well. That means you can implements any class:

class Greeter {
  final String name;
  Greeter(this.name);

  String greet(String who) => 'Hello $who, I am $name.';
}

class FakeGreeter implements Greeter {
  @override
  String get name => 'Test';

  @override
  String greet(String who) => 'Hi $who';
}

implements takes only the contract; none of Greeter's bodies come along. Notice that even the name field had to be written again. Fields are part of the interface too and must be implemented as getters (and as setters for mutable fields).

Dart 3: abstract interface class

Implementing a concrete class is possible, but it leaves your intent unclear. The interface modifier introduced in Dart 3 is the explicit way to say "this class is a contract." For a pure interface that contains only a contract, you write abstract interface class:

abstract interface class Eatable {
  void howToEat();
}

abstract interface class Squeezable {
  void howToSqueeze();
}

class Apple implements Eatable, Squeezable {
  @override
  void howToEat() => print('Bite and chew');

  @override
  void howToSqueeze() => print('Put it in a juicer and squeeze');
}

void main() {
  final apple = Apple();

  print('How do you eat an apple?');
  apple.howToEat();

  print('\nHow do you squeeze an apple?');
  apple.howToSqueeze();
}

Output:

How do you eat an apple?
Bite and chew

How do you squeeze an apple?
Put it in a juicer and squeeze

An earlier version of this post defined the interfaces as void howToSqueeze() {}, with empty braces. That small-looking difference matters: {} gives the method an empty body, so the method is no longer abstract. If someone extends such a class and forgets to write the method, the compiler says nothing, and a method that does nothing runs silently. When you're defining a contract, leave the method without a body and end it with a semicolon.

Why Two Separate Interfaces?

Because not every object has every ability. A stone can't be eaten but can be broken; bread can be eaten but not squeezed. Splitting abilities into small interfaces lets each class take on only the contracts it can actually fulfill:

abstract interface class Breakable {
  void howToBreak();
}

class Stone implements Breakable {
  @override
  void howToBreak() => print('With a hammer');
}

If you do the opposite and put eating, squeezing, peeling and drying methods into one big "Fruit" interface, every class that implements it has to fill the methods it doesn't need with empty bodies. @override methods left empty are a sign that the interface is too big.

The Difference Between extends and implements

With extends a subclass inherits the method body ready made, with implements a class takes only the contract and must write the body itself

extends implements with (mixin)
How many? Only one Several Several
Do bodies come along? Yes No Yes
Must you write every member? No, only the abstract ones Yes, all of them No
Relationship "Is a" "Can do" "Uses this behavior"

The code for the diagram's example uses the same Turkish names (Hayvan = animal, Kopek = dog, nefesAl = breathe, Yuruyebilir = can walk, yuru = walk):

class Hayvan {
  void nefesAl() => print('Breathing');
}

class Kopek extends Hayvan {
  // nefesAl came ready made; we may override it if we want
}

abstract interface class Yuruyebilir {
  void yuru();
}

class Robot implements Yuruyebilir {
  @override
  void yuru() => print('The robot walks'); // Must be written
}

A class can also use both at once, as in class Cat extends Animal implements Walkable.

The Same Abstract Class, Two Different Uses

This is the situation that confuses learners the most: an abstract class can be used with both extends and implements, and the result is different.

abstract class Shape {
  void draw() => print('Drawing a shape');
  double area();
}

class Square extends Shape {
  final double side;
  Square(this.side);

  @override
  double area() => side * side;
  // draw() came ready made
}

class Circle implements Shape {
  final double r;
  Circle(this.r);

  @override
  double area() => 3.14159 * r * r;

  @override
  void draw() => print('Drawing a circle'); // Must be written even though it has a body
}

Square, which uses extends, only writes the abstract area method. Circle, which uses implements, has to write everything, including draw, whose body was already there.

Dart 3 Modifiers: Who Can Do What?

Since Dart 3 you can decide how a class may be used from outside the library (usually the file) that defines it:

Declaration Can be instantiated? extends from outside implements from outside
class Yes Yes Yes
abstract class No Yes Yes
interface class Yes No Yes
abstract interface class No No Yes
base class Yes Yes No
final class Yes No No

If you're wondering why interface class exists: when a class's methods call each other, someone who extends it from outside and overrides one method can break the others in unexpected ways. interface closes off that risk; using and implementing the class is allowed, but nobody can inherit from it and change its internals. These restrictions matter most when you write a package; inside a single app, the form you'll use most is abstract interface class.

Mixins: Ready-Made Behavior, Not a Contract

Sometimes what you need isn't a contract but a piece of code that repeats across several classes. Instead of copying a behavior like logging into every class, you use a mixin:

mixin Logger {
  void log(String message) => print('[$runtimeType] $message');
}

class PaymentService with Logger {
  void pay(double amount) {
    log('Payment of \$$amount started');
  }
}

class CartService with Logger {
  void clear() => log('Cart cleared');
}

void main() {
  PaymentService().pay(250); // [PaymentService] Payment of $250.0 started
  CartService().clear();     // [CartService] Cart cleared
}

If a mixin should only be used with a particular type, you restrict it with on. That lets the mixin access that type's members:

abstract class Animal {
  String get name;
}

mixin Swimmer on Animal {
  void swim() => print('$name is swimming');
}

class Duck extends Animal with Swimmer {
  @override
  String get name => 'Duck';
}

void main() {
  Duck().swim(); // Duck is swimming
}

A pattern common in older tutorials no longer works in Dart 3: mixing in a plain class declaration with with. In Dart 3 a class can only be used as a mixin if it's declared as mixin or mixin class. A mixin class can be used both as a class and as a mixin; Flutter's ChangeNotifier is declared this way, which is why you can write either extends ChangeNotifier or with ChangeNotifier. In your own code, following Effective Dart's advice and declaring either a pure mixin or a pure class is clearer.

Interfaces in Real Projects: Testable Code

The most valuable use of interfaces in everyday Flutter development is making a dependency replaceable. Think of a layer that fetches user data. In the app it loads the data from a server; in tests it should return fixed data without ever touching the server:

class User {
  final int id;
  final String name;
  const User(this.id, this.name);
}

abstract interface class UserRepository {
  Future<User?> fetch(int id);
}

class FakeUserRepository implements UserRepository {
  @override
  Future<User?> fetch(int id) async =>
      id == 1 ? const User(1, 'Ayse') : null;
}

class ProfileService {
  final UserRepository repository; // Depends on the contract, not a concrete class

  ProfileService(this.repository);

  Future<String> title(int id) async {
    final user = await repository.fetch(id);
    return user?.name ?? 'Unknown user';
  }
}

Future<void> main() async {
  final service = ProfileService(FakeUserRepository());
  print(await service.title(1)); // Ayse
  print(await service.title(2)); // Unknown user
}

ProfileService doesn't know where the data comes from; it only knows the UserRepository contract. In the app, an ApiUserRepository class that sends an HTTP request implements the same contract, and in tests FakeUserRepository is used. Here composition and interfaces work together: the service has a repository, and which class that repository is gets decided from outside. For details on async and await, see try-catch and async/await.

Flutter itself works on this idea. For example, ChangeNotifier, ValueNotifier and animation controllers all implement the shared Listenable contract, which is why any of them can be handed to the same widget, such as ListenableBuilder. You also don't need to write your own "tappable" interface for touch events; GestureDetector takes care of that.

Help from Your Editor

When you type implements Eatable on a class in VS Code or Android Studio, an error marker appears because of the missing methods. The quick-fix menu (the light bulb icon) offers to create the missing overrides and adds all the method signatures for you. Then you only fill in the bodies.

Common Mistakes

  • Defining interface methods with {}; the method stops being abstract, and forgotten implementations slip through silently.
  • Designing one big interface and filling the implementing classes with empty methods.
  • Using implements just to share code; if you need ready-made behavior, a mixin or composition fits better.
  • Trying to use a plain class with with in Dart 3; you need mixin or mixin class.
  • Tying services to concrete classes; code that depends on a contract stays testable.

Next Step

Now that you can design objects, we can move on to ways of keeping them together: Dart collections: List, Set and Map. The same distinction exists in C#; you'll find the counterpart in the post on interfaces vs. abstract classes.

Resources


If you want to work with me:

Frequently Asked Questions

Does Dart have a separate interface keyword?

In Dart every class implicitly defines an interface, so you can implement any class. The interface modifier introduced in Dart 3 prevents a class from being extended outside its own library and allows only implements. For a pure contract, write abstract interface class.

Can a class use both extends and implements?

Yes. With something like class Cat extends Animal with Swimmer implements Walkable, Runnable you can derive from one superclass, use a mixin and implement several interfaces at the same time. The order is always extends, with, implements.

Can an abstract class contain methods with a body?

Yes, an abstract class can contain both methods with a body and abstract methods without one. Classes that extend it get the implemented methods ready made; classes that implement it have to rewrite every method, with or without a body.

What is the difference between a mixin and implements?

implements takes only the contract and requires you to write every body. A mixin (with) adds the bodies to the class ready made. If you want to share a behavior's code, use a mixin; if you want to guarantee that a class has a certain ability, use an interface.

Comments