What are Interfaces?
An interface defines a contract — a set of methods and properties that a class promises to implement. Unlike inheritance (extends), implementing an interface does not share any code. The implementing class must provide its own implementation of every member. Dart has no dedicated interface keyword — every class implicitly defines an interface.
Implicit Interfaces
In Dart, every class automatically has an implicit interface. Any other class can implement it using implements. When you implement a class, you must provide your own implementation of ALL public members — you get no inherited code:
class Animal {
String name;
Animal(this.name);
void breathe() => print(''$name breathes'');
void eat() => print(''$name eats'');
}
// Implements Animal''s interface — must define ALL methods
class Robot implements Animal {
@override
String name; // must declare all fields too
Robot(this.name);
@override
void breathe() => print(''$name uses power cells'');
@override
void eat() => print(''$name recharges'');
}
implements Keyword
abstract class Printable {
void print();
}
abstract class Saveable {
Future<bool> save(String path);
}
abstract class Shareable {
void share(String recipient);
}
class Document implements Printable, Saveable, Shareable {
String content;
Document(this.content);
@override
void print() => dart_core.print(''Printing: $content'');
@override
Future<bool> save(String path) async {
dart_core.print(''Saving to $path'');
return true;
}
@override
void share(String recipient) =>
dart_core.print(''Sharing with $recipient'');
}
Implementing Multiple Interfaces
A class can implement multiple interfaces simultaneously — this is one of the key advantages of interfaces over inheritance:
abstract class Flyable {
void fly();
double get maxAltitude;
}
abstract class Swimmable {
void swim();
double get maxDepth;
}
abstract class Runnable {
void run();
double get maxSpeed;
}
// Duck implements all three
class Duck implements Flyable, Swimmable, Runnable {
final String name;
Duck(this.name);
@override void fly() => print(''$name is flying!'');
@override void swim() => print(''$name is swimming!'');
@override void run() => print(''$name is running!'');
@override double get maxAltitude => 100.0;
@override double get maxDepth => 2.0;
@override double get maxSpeed => 10.0;
}
// Penguin can only swim and run
class Penguin implements Swimmable, Runnable {
final String name;
Penguin(this.name);
@override void swim() => print(''$name swims fast!'');
@override void run() => print(''$name waddles!'');
@override double get maxDepth => 500.0;
@override double get maxSpeed => 5.0;
}
void main() {
var duck = Duck(''Donald'');
duck.fly(); // Donald is flying!
duck.swim(); // Donald is swimming!
var penguin = Penguin(''Tux'');
penguin.swim(); // Tux swims fast!
// Type checking
print(duck is Flyable); // true
print(penguin is Flyable); // false
print(penguin is Swimmable); // true
}
Output:Donald is flying!
Donald is swimming!
Tux swims fast!
true
false
true
Abstract Classes as Interfaces
The most common pattern in Dart is to use abstract class to define interfaces. This gives you type checking via is and as, and you can add concrete helper methods:
abstract class Repository<T> {
Future<T?> findById(int id);
Future<List<T>> findAll();
Future<T> save(T entity);
Future<bool> delete(int id);
// Concrete helper — uses abstract methods
Future<bool> exists(int id) async {
return await findById(id) != null;
}
}
class UserRepository implements Repository<Map<String, dynamic>> {
final Map<int, Map<String, dynamic>> _store = {};
int _nextId = 1;
@override
Future<Map<String, dynamic>?> findById(int id) async => _store[id];
@override
Future<List<Map<String, dynamic>>> findAll() async => _store.values.toList();
@override
Future<Map<String, dynamic>> save(Map<String, dynamic> entity) async {
var id = entity[''id''] as int? ?? _nextId++;
_store[id] = {...entity, ''id'': id};
return _store[id]!;
}
@override
Future<bool> delete(int id) async => _store.remove(id) != null;
}
void main() async {
var repo = UserRepository();
await repo.save({''name'': ''Alice'', ''email'': ''alice@example.com''});
await repo.save({''name'': ''Bob'', ''email'': ''bob@example.com''});
print(await repo.findAll());
print(await repo.exists(1)); // true
print(await repo.exists(99)); // false
}
Exercise
- Define abstract classes
Readable, Writable, and Seekable. Create a FileStream class that implements all three.
- Define a
Comparable<T>-like interface with compareTo(T other). Implement it on a Student class that compares by GPA.
Quiz
1. In Dart, what keyword is used to implement an interface?
- A) extends
- B) interface
- C) implements ✅
- D) uses
2. When you implement a class, do you inherit its method bodies?
- A) Yes, all of them
- B) Yes, only public ones
- C) No — you must provide your own implementation for all members ✅
- D) Only if you use @override
3. Can a Dart class implement more than one interface?
- A) No — only one
- B) Yes — multiple with implements A, B, C ✅
- C) Only two
- D) Only if they are abstract
Summary
Every Dart class implicitly defines an interface. Use implements to adopt the contract of one or more classes without inheriting their code. The implementing class must override every public member. Abstract classes are the preferred way to define interfaces in Dart because they support both abstract method signatures and concrete helper methods. Multiple interfaces can be implemented simultaneously, unlike the single-parent restriction of extends.