| |

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

FeatureSyntax
Declare mixinmixin Name { }
Abstract mixin classabstract mixin class Name { }
Apply mixinclass C with Mixin { }
Multiple mixinsclass C with A, B, C { }
Extend and mixclass C extends Base with Mixin { }
Constraintmixin M on Base { }
Multiple constraintsmixin M on A, B { }

Mixin vs Other Mechanisms

MechanismProvidesRelationship
extendsInterface + implementation“is a”
implementsInterface only“behaves like”
withImplementation only“has behavior”

Member Resolution Order

PrioritySource
1Class’s own members
2Last mixin
3Earlier mixins
4Superclass

Constraints

ConstraintMeaning
mixin M on AM can only be applied to classes that are A
mixin M on A, BM requires both A and B
mixin M on A with class C with A, MValid order
mixin M on A with class C with M, AInvalid 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

PitfallWhy It HappensFix
Mixin cannot be instantiatedMixins are not classesApply to a class
Conflict on fieldSame field in two mixinsRename or use getter
on constraint not satisfiedClass does not extend the required typeAdd the required superclass
Wrong application orderMixin depends on anotherList dependencies first
Method shadowed unexpectedlyLast mixin winsReorder or override
super unresolvedNo superclass or earlier mixinAdd a base or check order
State duplicatedField in multiple mixinsUse 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

ItemValue
Declare mixinmixin Name { }
Apply mixinclass C with Mixin { }
Multiple mixinsclass C with A, B { }
Extend and mixclass C extends Base with Mixin { }
Constraintmixin M on Base { }
Multiple constraintsmixin M on A, B { }
Abstract mixin classabstract mixin class Name { }
Member resolutionClass, then last mixin, then earlier mixins
super in mixinChains to previous mixin or superclass
FieldsDuplicated if same name in two mixins
ConstructorNot allowed in mixins
InstantiationNot 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.
  • with applies mixins; multiple mixins are listed in order. The order matters when mixins define the same member: the last applied wins. class C with A, B resolves greet to B‘s version.
  • The on clause constrains a mixin to a type. mixin M on Base can only be applied to classes that extend or implement Base. The mixin can use members declared on Base, and the constraint makes the dependency explicit.
  • Mixins compose through super. A mixin can call super.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 for State mixins.
  • 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 extends clause comes first, then with. The member resolution order is: class, last mixin, earlier mixins, superclass.
  • abstract mixin class combines both roles. A class declared with abstract mixin class can 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!