Mixins

What Are Mixins

A Mixin is a code reuse mechanism that lets you share code across unrelated class inheritance hierarchies. Its core purpose is to inject member implementations into classes in bulk. It resembles multiple inheritance, but it’s designed to attach modular feature sets to a class.

Using Mixins

Use the with keyword followed by one or more Mixin names.

class Musician extends Performer with Musical {
  // Code here
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}
Code language: Dart (dart)

Defining Mixins

Declare a mixin using the mixin keyword.

Note: mixin class (Dart 3.0+) can function both as a regular class and a Mixin.

Mandatory Restrictions

  1. mixin / mixin class cannot use extends
  2. No generative constructors may be declared
mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

class Musician with Musical {}

void main() {
  var m = Musician();
  m.canConduct = true;
  m.entertainMe(); // Waving hands
}
Code language: Dart (dart)

Execution Output

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Musician is playing
Musician is playingCode language: CSS (css)

Mixins Calling External Members

Methods / properties the Mixin itself cannot define

Mixins often need to invoke members they don’t implement themselves. Below are four standard approaches.

Declare Abstract Members Inside the Mixin

Define abstract methods within the Mixin. Every class using this Mixin must implement those abstract methods.

mixin Musician {
  // Abstract method to be implemented by consumers
  void playInstrument(String instrumentName);

  void playPiano() {
    playInstrument('Piano');
  }
  void playFlute() {
    playInstrument('Flute');
  }
}

// Consumers must override and implement
class Virtuoso with Musician {
  @override
  void playInstrument(String instrumentName) {
    print('Plays the $instrumentName beautifully');
  }
}

void main() {
  var artist = Virtuoso();
  artist.playPiano();
  artist.playFlute();
}
Code language: Dart (dart)
Plays the Piano beautifully
Plays the Flute beautifully

Access Host Class State via Abstract Getters

The Mixin defines abstract getters to read fields belonging to the host class, enabling cross-class state access.

/// Can be mixed into any class with a name field, automatically supplying == and hashCode based on name
mixin NameIdentity {
  String get name;

  @override
  int get hashCode => name.hashCode;

  @override
  bool operator ==(Object other) =>
      other is NameIdentity && name == other.name;
}

class Person with NameIdentity {
  final String name;
  Person(this.name);
}

void main() {
  var p1 = Person("Alice");
  var p2 = Person("Alice");
  var p3 = Person("Bob");
  print(p1 == p2); // true
  print(p1 == p3); // false
}
Code language: JavaScript (javascript)

Mixin Implementing Interfaces with implements

Use implements on a Mixin to enforce an interface contract, forcing mixing classes to implement all interface methods.

abstract interface class Tuner {
  void tuneInstrument();
}

mixin Guitarist implements Tuner {
  void playSong() {
    tuneInstrument();
    print('Strums guitar majestically.');
  }
}

class PunkRocker with Guitarist {
  @override
  void tuneInstrument() {
    print("Don't bother, being out of tune is punk rock.");
  }
}

void main() {
  PunkRocker().playSong();
}
Code language: JavaScript (javascript)
Don't bother, being out of tune is punk rock.
Strums guitar majestically.

on Clause: Restrict Mixins to Subclasses of Specific Parent Types

The on keyword limits usage: only subclasses inheriting from the specified type may apply this Mixin.

Additionally, the Mixin can invoke parent class members and call super.xxx().

Only use on when you need to call parent methods via super inside the Mixin!

class Musician {
  void musicianMethod() {
    print('Playing music!');
  }
}

// Can only be mixed into subclasses of Musician
mixin MusicalPerformer on Musician {
  void performerMethod() {
    print('Performing music!');
    super.musicianMethod(); // Call parent methods constrained by on clause
  }
}

// SingerDancer extends Musician so it can use MusicalPerformer
class SingerDancer extends Musician with MusicalPerformer {}

void main() {
  SingerDancer().performerMethod();
}
Code language: Dart (dart)
Performing music!
Playing music!

SingerDancer mixes MusicalPerformer, so it gains access to the performerMethod defined inside it.

Mixing works in this fashion: a class imports all methods defined inside another Mixin and can use them directly.

⚠️ Incorrect Example:

If a class does not inherit Musician, directly writing with MusicalPerformer triggers a compile error.


Differences Between class / mixin / mixin class

  1. mixin: Can only be applied via with, cannot be inherited via extends
  2. class: Supports extends, cannot be used directly as a Mixin
  3. mixin class:
    • Can act as a Mixin using with
    • Can also serve as a normal parent class via extends

The same constraints apply:

  • mixin / mixin class cannot declare extends or with
  • class / mixin class cannot use on
mixin class Musician {
  void play() {
    print("Musician is playing");
  }
}

// Used as Mixin
class Novice1 with Musician {}

// Used as regular parent class
class Novice2 extends Musician {}

void main() {
  Novice1().play();
  Novice2().play();
}
Code language: Dart (dart)

Running Result

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Musician is playing
Musician is playing
Code language: CSS (css)

Mixins

Leave a Reply

Your email address will not be published. Required fields are marked *