Dart 26 🎯 Mixins (mixin, with, on) and Multiple Code Reuse Architecture
A class can extend only one class. Dart has no multiple inheritance. But an application often needs to share behavior across classes that are not related by inheritance. A Logger might need to be mixed into a service, a repository, and a controller. These classes do not share a superclass, and forcing them into one would create a hierarchy that does not reflect the domain. Mixins solve this. They let a class adopt the implementation of another type without establishing an “is a” relationship. The class gains the members, and the type system records that it has them.
This chapter covers Dart’s mixin system in full. You will learn the mixin declaration, the with clause that applies one or more mixins to a class, and the on constraint that restricts which classes can use a mixin. You will see how mixins differ from abstract classes and interfaces, how they compose when multiple are applied, and the patterns that make mixins maintainable. You will also see the pitfalls—diamond ambiguity, state duplication, and the temptation to use mixins for everything.
Key point: A mixin is a collection of members that can be added to a class. It is declared with mixin and applied with with. A class can use multiple mixins, and the mixins are applied in order. The on clause restricts a mixin to classes that extend or implement a specific type. Mixins are not a substitute for inheritance or composition; they are a third mechanism for a specific kind of reuse.
Why mixins exist
The single inheritance problem. Dart classes extend exactly one superclass. If behavior is shared across unrelated classes, inheritance cannot express the sharing without forcing an artificial hierarchy. A UserService and a ProductService might both need logging, but neither is a kind of Logger. Forcing both to extend a BaseService that includes logging works until a third service needs a different combination of behaviors. Mixins let each class select the behaviors it needs.
The interface-only problem. An abstract class or interface declares a contract but provides no implementation. If the shared behavior has a default implementation—a log method that writes to stdout, a serialize method that produces JSON—the interface cannot provide it. Every implementing class would duplicate the implementation. A mixin provides both the declaration and the implementation, so the class gains the method without redefining it.
The composition problem. Composition—holding a reference to another object—is often the better choice for “has a” relationships. A service that needs logging can hold a Logger instance and call logger.log(...). But composition requires every call site to delegate. this.log(...) becomes this.logger.log(...). For cross-cutting concerns like logging, timing, and serialization, the delegation is repetitive. A mixin makes the method available directly on the class.
The diamond problem. Multiple inheritance in C++ produces the diamond problem: two superclasses share a common ancestor, and the subclass inherits the ancestor twice. Dart avoids multiple inheritance but allows multiple mixins. The on clause and the linear application order prevent the diamond ambiguity. Each mixin is applied in sequence, and the last one wins when there is a conflict.
The framework problem. Flutter uses mixins extensively. SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin, and WidgetsBindingObserver are all mixins that add behavior to widget classes. Understanding mixins is a prerequisite for working with Flutter.
a. Declaring and applying mixins
A mixin is declared with the mixin keyword. It can contain methods, getters, setters, and fields. It cannot have a constructor.
mixin Logger {
void log(String message) {
print('[LOG] $message');
}
void error(String message) {
print('[ERROR] $message');
}
}
The Logger mixin provides two methods. It has no constructor because a mixin cannot be instantiated on its own. It exists to be applied to a class.
A class applies a mixin with the with clause:
class UserService with Logger {
void createUser(String name) {
log('Creating user: $name');
// ...
}
}
The UserService class has the log and error methods from the Logger mixin. It does not extend Logger, and there is no inheritance relationship. The mixin’s implementation is copied into the class’s member set.
Multiple mixins are applied with a comma-separated list:
mixin Timestamp {
void timestamp() {
print(DateTime.now().toIso8601String());
}
}
class UserService with Logger, Timestamp {
void createUser(String name) {
log('Creating user: $name');
timestamp();
}
}
The class has the members of both mixins. The order matters when the mixins define the same member: the last one in the list wins.
A class can extend a superclass and apply mixins at the same time:
class BaseService {
void initialize() {
print('Initializing');
}
}
class UserService extends BaseService with Logger, Timestamp {
// has initialize from BaseService
// has log and error from Logger
// has timestamp from Timestamp
}
The extends clause comes first, then with. The class inherits from the superclass and adopts the mixins. The member resolution order is: the class’s own members, then the mixins in reverse order (last applied wins), then the superclass.
A mixin can be declared with abstract mixin to allow abstract methods:
abstract mixin class Serializable {
Map<String, dynamic> toJson();
String serialize() => jsonEncode(toJson());
}
An abstract mixin class can be used as both a mixin and a superclass. This is a Dart 3 feature that provides more flexibility than a plain mixin.
b. The on clause and mixin constraints
The on clause restricts a mixin to classes that extend or implement a specific type. This is used when the mixin depends on members that the constrained type provides.
abstract class Entity {
int get id;
}
mixin Identifiable on Entity {
String get identifier => 'entity-$id';
}
The Identifiable mixin can only be applied to a class that extends or implements Entity. The mixin uses the id getter, which is declared on Entity. The on clause ensures that any class using the mixin has the id getter.
class User extends Entity with Identifiable {
@override
final int id;
User(this.id);
}
The User class extends Entity, so it satisfies the on constraint. The mixin’s identifier getter uses the id field.
A mixin can have multiple constraints:
mixin Persistent on Entity, Serializable {
void save() {
print('Saving ${serialize()}');
}
}
The Persistent mixin requires the class to be both an Entity and a Serializable. It can use members from both.
The on clause can reference a type that is a mixin itself:
mixin A {
void methodA() {}
}
mixin B on A {
void methodB() {
methodA();
}
}
class C with A, B {}
The B mixin can only be applied to a class that already has A. The class C applies A first, then B, satisfying the constraint. The order in the with clause matters: the constraint must be satisfied by the mixins that come before.
The on clause is not just a constraint. It is also a declaration of dependency. A reader of the mixin can see what it requires without reading the implementation. This makes the mixin’s contract explicit.
c. Mixin composition and conflicts
When multiple mixins are applied, the members are resolved in a specific order. The class’s own members take precedence. Then the mixins, with the last-applied mixin winning. Then the superclass.
mixin A {
void greet() => print('A');
}
mixin B {
void greet() => print('B');
}
class C with A, B {}
void main() {
C().greet(); // 'B'
}
Both A and B define greet. The class C applies A then B. The last applied is B, so C().greet() prints 'B'. The A version is shadowed.
The class’s own override takes precedence over all mixins:
class C with A, B {
@override
void greet() => print('C');
}
C().greet() now prints 'C'.
A mixin can call super to access the member from the previous mixin or the superclass:
mixin A {
void greet() => print('A');
}
mixin B {
void greet() {
super.greet();
print('B');
}
}
class C with A, B {}
void main() {
C().greet();
// A
// B
}
The B mixin calls super.greet(), which resolves to the A mixin’s implementation. The output is 'A' followed by 'B'. This is how mixins compose: each one can extend the behavior of the previous one.
The super call in a mixin is resolved at the point of application. The mixin itself does not know what super refers to. The class that applies it determines the resolution order. This is the same mechanism that Flutter uses for State mixins, where a mixin can call super.dispose() to chain cleanup.
Mixins can have fields, but the fields are duplicated if multiple mixins define the same field. Unlike methods, fields cannot be overridden—they are separate storage. A mixin with a field adds that field to the class. Two mixins with the same field name produce a conflict that the compiler reports.
mixin A {
int value = 1;
}
mixin B {
int value = 2; // conflict
}
// class C with A, B {} // error: 'value' is defined in both
The fix is to rename one of the fields or to use a getter instead of a field. Getters can be overridden; fields cannot.
The composition order is called “linearization.” The class’s member resolution is a linear chain: the class, then the last mixin, then the previous mixin, and so on, ending at the superclass. The super call in each link goes to the next link in the chain. This is the same model that Dart uses for its class hierarchy, extended to mixins.
Complete Example Session
// ============================================
// PART 1: BASIC MIXIN
// ============================================
mixin Logger {
void log(String message) {
print('[LOG] $message');
}
}
class Service with Logger {
void run() {
log('Service running');
}
}
// ============================================
// PART 2: MULTIPLE MIXINS
// ============================================
mixin Timestamp {
void printTimestamp() {
print(DateTime.now().toIso8601String());
}
}
class Service with Logger, Timestamp {
void run() {
log('Running');
printTimestamp();
}
}
// ============================================
// PART 3: EXTENDS AND WITH
// ============================================
class BaseService {
void initialize() => print('Initialized');
}
class UserService extends BaseService with Logger {
void createUser(String name) {
log('Creating $name');
}
}
// ============================================
// PART 4: MIXIN WITH ON CLAUSE
// ============================================
abstract class Entity {
int get id;
}
mixin Identifiable on Entity {
String get identifier => 'entity-$id';
}
class User extends Entity with Identifiable {
@override
final int id;
User(this.id);
}
// ============================================
// PART 5: MULTIPLE CONSTRAINTS
// ============================================
abstract class Serializable {
String serialize();
}
mixin Persistent on Entity, Serializable {
void save() {
print('Saving ${serialize()}');
}
}
// ============================================
// PART 6: MIXIN ORDER AND CONFLICT
// ============================================
mixin A {
void greet() => print('A');
}
mixin B {
void greet() => print('B');
}
class C with A, B {}
// C().greet() prints 'B' (last applied wins)
// ============================================
// PART 7: SUPER IN MIXINS
// ============================================
mixin A {
void greet() => print('A');
}
mixin B {
void greet() {
super.greet();
print('B');
}
}
class C with A, B {}
// C().greet() prints 'A' then 'B'
// ============================================
// PART 8: CLASS OVERRIDE TAKES PRECEDENCE
// ============================================
class D with A, B {
@override
void greet() => print('D');
}
// D().greet() prints 'D'
// ============================================
// PART 9: MIXIN WITH FIELD
// ============================================
mixin Counter {
int count = 0;
void increment() {
count++;
}
}
class Clicker with Counter {
void click() {
increment();
print('Clicks: $count');
}
}
// ============================================
// PART 10: ABSTRACT MIXIN CLASS (DART 3)
// ============================================
abstract mixin class JsonSerializable {
Map<String, dynamic> toJson();
String serialize() => jsonEncode(toJson());
}
class Product with JsonSerializable {
final String name;
final double price;
Product(this.name, this.price);
@override
Map<String, dynamic> toJson() => {
'name': name,
'price': price,
};
}
The ten parts covered basic mixins, multiple mixins, extends with with, the on clause, multiple constraints, mixin order and conflict, super in mixins, class override precedence, mixin fields, and abstract mixin classes.
Quick Reference
Mixin Syntax
| Feature | Syntax |
|---|---|
| Declare mixin | mixin Name { } |
| Abstract mixin class | abstract mixin class Name { } |
| Apply mixin | class C with Mixin { } |
| Multiple mixins | class C with A, B, C { } |
| Extend and mix | class C extends Base with Mixin { } |
| Constraint | mixin M on Base { } |
| Multiple constraints | mixin M on A, B { } |
Mixin vs Other Mechanisms
| Mechanism | Provides | Relationship |
|---|---|---|
extends | Interface + implementation | “is a” |
implements | Interface only | “behaves like” |
with | Implementation only | “has behavior” |
Member Resolution Order
| Priority | Source |
|---|---|
| 1 | Class’s own members |
| 2 | Last mixin |
| 3 | Earlier mixins |
| 4 | Superclass |
Constraints
| Constraint | Meaning |
|---|---|
mixin M on A | M can only be applied to classes that are A |
mixin M on A, B | M requires both A and B |
mixin M on A with class C with A, M | Valid order |
mixin M on A with class C with M, A | Invalid order |
Best Practices
✅ Do This:
// Use mixins for cross-cutting behavior
mixin Logger {
void log(String message) => print('[LOG] $message');
} // ✅
// Use the on clause to declare dependencies
mixin Identifiable on Entity {
String get identifier => 'entity-$id';
} // ✅
// Apply multiple mixins for combined behavior
class Service with Logger, Timestamp { } // ✅
// Use super in mixins for composition
mixin B {
void greet() {
super.greet();
print('B');
}
} // ✅
// Use getters instead of fields when possible
mixin Counter {
int get count => _count;
int _count = 0;
} // ✅
// Use abstract mixin class when both roles are needed
abstract mixin class Serializable {
Map<String, dynamic> toJson();
} // ✅
❌ Don’t Do This:
// Don't use mixins for "is a" relationships
mixin Vehicle { }
class Car with Vehicle { } // Car IS NOT A Vehicle // ❌
// Don't define the same field in two mixins
mixin A { int x = 0; }
mixin B { int x = 0; }
class C with A, B { } // conflict // ❌
// Don't forget the on clause when the mixin needs members
mixin M {
void f() => print(id); // id not defined // ❌
}
// Don't apply mixins in the wrong order
class C with B, A { } // if B requires A, this fails // ❌
// Don't use mixins for everything
// Use composition when the relationship is "has a" // ⚠️
// Don't instantiate a mixin
// var logger = Logger(); // error // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Mixin cannot be instantiated | Mixins are not classes | Apply to a class |
| Conflict on field | Same field in two mixins | Rename or use getter |
on constraint not satisfied | Class does not extend the required type | Add the required superclass |
| Wrong application order | Mixin depends on another | List dependencies first |
| Method shadowed unexpectedly | Last mixin wins | Reorder or override |
super unresolved | No superclass or earlier mixin | Add a base or check order |
| State duplicated | Field in multiple mixins | Use one mixin or getters |
Real-World Examples
1. Logger Mixin
mixin Logger {
void log(String message) => print('[LOG] $message');
}
class UserService with Logger {
void createUser(String name) => log('Creating $name');
}
2. Serializable Mixin
mixin Serializable {
Map<String, dynamic> toJson();
String serialize() => jsonEncode(toJson());
}
3. Timestamp Mixin
mixin Timestamp {
String get timestamp => DateTime.now().toIso8601String();
}
4. Identifiable Mixin
mixin Identifiable on Entity {
String get identifier => 'entity-$id';
}
5. Validatable Mixin
mixin Validatable {
bool get isValid;
List<String> get errors;
}
6. Retryable Mixin
mixin Retryable {
Future<T> retry<T>(Future<T> Function() action, {int attempts = 3}) async {
for (var i = 0; i < attempts; i++) {
try {
return await action();
} catch (e) {
if (i == attempts - 1) rethrow;
}
}
throw StateError('unreachable');
}
}
7. Cacheable Mixin
mixin Cacheable<K, V> {
final _cache = <K, V>{};
V? getCached(K key) => _cache[key];
void cache(K key, V value) => _cache[key] = value;
}
8. Observable Mixin
mixin Observable {
final _listeners = <void Function()>[];
void addListener(void Function() listener) => _listeners.add(listener);
void notifyListeners() => _listeners.forEach((l) => l());
}
9. Disposable Mixin
mixin Disposable {
bool _disposed = false;
bool get isDisposed => _disposed;
void dispose() {
_disposed = true;
}
}
10. Flutter State Mixin
class _MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Visual
Inheritance vs Interface vs Mixin
┌─────────────────────────────────────────────────────────────┐
│ EXTENDS (IS A) │
│ │
│ class Dog extends Animal { } │
│ │
│ Dog IS AN Animal. │
│ Inherits interface AND implementation. │
│ Single inheritance only. │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ IMPLEMENTS (BEHAVES LIKE) │
│ │
│ class MockRepo implements Repository { } │
│ │
│ MockRepo behaves like Repository. │
│ Inherits interface ONLY. │
│ Multiple interfaces allowed. │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ WITH (HAS BEHAVIOR) │
│ │
│ class Service with Logger { } │
│ │
│ Service HAS the behavior of Logger. │
│ Inherits implementation ONLY. │
│ Multiple mixins allowed. │
│ No "is a" relationship. │
│ │
└─────────────────────────────────────────────────────────────┘
Mixin Application Order
┌─────────────────────────────────────────────────────────────┐
│ class C with A, B { } │
│ │
│ Member resolution order (linearization): │
│ │
│ 1. C's own members │
│ 2. B (last applied) │
│ 3. A │
│ 4. Object (superclass) │
│ │
│ If A and B both define greet: │
│ B.greet wins (last applied). │
│ │
│ If C defines greet: │
│ C.greet wins (class takes precedence). │
│ │
│ If B calls super.greet(): │
│ Resolves to A.greet. │
│ │
└─────────────────────────────────────────────────────────────┘
The on Clause
┌─────────────────────────────────────────────────────────────┐
│ abstract class Entity { int get id; } │
│ │
│ mixin Identifiable on Entity { │
│ String get identifier => 'entity-$id'; │
│ } │
│ │
│ The mixin requires Entity. │
│ It can use the id getter. │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ class User extends Entity with Identifiable { } │ │
│ │ ✓ Valid: User extends Entity │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ class Product with Identifiable { } │ │
│ │ ✗ Error: Product does not extend Entity │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
super in Mixins
┌─────────────────────────────────────────────────────────────┐
│ mixin A { │
│ void greet() => print('A'); │
│ } │
│ │
│ mixin B { │
│ void greet() { │
│ super.greet(); │
│ print('B'); │
│ } │
│ } │
│ │
│ class C with A, B {} │
│ │
│ C().greet() │
│ │ │
│ ▼ │
│ B.greet() runs: │
│ │ │
│ ├── super.greet() → A.greet() → prints 'A' │
│ │ │
│ └── prints 'B' │
│ │
│ Output: │
│ A │
│ B │
│ │
│ super chains through the mixin order. │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Declare mixin | mixin Name { } |
| Apply mixin | class C with Mixin { } |
| Multiple mixins | class C with A, B { } |
| Extend and mix | class C extends Base with Mixin { } |
| Constraint | mixin M on Base { } |
| Multiple constraints | mixin M on A, B { } |
| Abstract mixin class | abstract mixin class Name { } |
| Member resolution | Class, then last mixin, then earlier mixins |
| super in mixin | Chains to previous mixin or superclass |
| Fields | Duplicated if same name in two mixins |
| Constructor | Not allowed in mixins |
| Instantiation | Not allowed |
Key takeaways:
- Mixins provide implementation, not inheritance. A class that uses a mixin gains the mixin’s members without establishing an “is a” relationship. The mixin’s code is copied into the class’s member set.
withapplies mixins; multiple mixins are listed in order. The order matters when mixins define the same member: the last applied wins.class C with A, BresolvesgreettoB‘s version.- The
onclause constrains a mixin to a type.mixin M on Basecan only be applied to classes that extend or implementBase. The mixin can use members declared onBase, and the constraint makes the dependency explicit. - Mixins compose through
super. A mixin can callsuper.method()to invoke the implementation from the previous mixin or the superclass. This is how mixins chain behavior, and it is the mechanism Flutter uses forStatemixins. - Fields cannot be overridden; they are duplicated. Two mixins with the same field name produce a conflict. Methods can be overridden because they participate in the linearization. Use getters instead of fields when the value should be overridable.
- A class can extend one superclass and apply multiple mixins. The
extendsclause comes first, thenwith. The member resolution order is: class, last mixin, earlier mixins, superclass. abstract mixin classcombines both roles. A class declared withabstract mixin classcan be used as a superclass or as a mixin. This is a Dart 3 feature that provides flexibility when a type needs to serve both purposes.- Mixins are for cross-cutting concerns. Logging, serialization, caching, and observability are typical mixin use cases. They are not a substitute for inheritance, and they are not a substitute for composition. Use them when the behavior is genuinely shared across unrelated types and the delegation would be repetitive.
Remember: Dart’s mixin system solves a specific problem: sharing behavior across classes that are not related by inheritance. It does this without the diamond problem of multiple inheritance and without the verbosity of composition. A mixin is declared with mixin, applied with with, and constrained with on. Multiple mixins compose in order, and super chains through them. The order matters, fields conflict, and the on clause makes dependencies explicit. Use mixins for cross-cutting concerns, use inheritance for “is a” relationships, use interfaces for contracts, and use composition for “has a” relationships. Each mechanism has its place, and knowing which one to reach for is the design decision that matters most.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!