Dart mixins code shown on a laptop screen in a dark editor

Dart Mixins for Beginners: The with Keyword Made Simple

You finished the inheritance post, built a Book class, and gave Ebook and Audiobook a shared parent. Then you hit the wall every Dart beginner eventually hits: a class can only extend one thing. Now you have three widgets that all need to log what they do, and a couple of game characters that both need to save and load, but none of them share a sensible parent. You could copy the same methods into every class, or bolt on a fake base class nobody believes in. Dart mixins are the honest way out. A mixin is a bundle of methods you write once and mix into any class that needs them, no shared parent required. By the end of this post you will declare a mixin, add it to a class with the with keyword, stack several at once, use the on keyword to give a mixin the context it needs, and know when a mixin is the wrong call.

What a Dart mixin is, and the with keyword

Start with the smallest version that does anything useful. A mixin looks almost like a class, but you declare it with the mixin keyword instead of class, and you never create one directly. You attach it to a real class with with.

mixin Swimmer {
  void swim() => print('Swimming');
}

class Fish with Swimmer {}

void main() {
  var nemo = Fish();
  nemo.swim(); // Swimming
}

Run that and Fish prints Swimming, even though Fish itself is empty. The swim method came from Swimmer, and Fish picked it up the moment you wrote with Swimmer. That is the whole idea in one line: with pulls a mixin’s methods into your class as if you had typed them there yourself. Notice you never wrote Swimmer() anywhere. You cannot. A mixin has no constructor and cannot be built on its own, it exists only to be mixed in. If you have read the Dart classes post, this is the piece that feels different: a mixin is behavior without an object of its own.

Stacking mixins, and why the order matters

The reason mixins beat a single parent class is that you can add as many as you want. List them after with, separated by commas, and the class gets all of them.

mixin Swimmer {
  void swim() => print('Swimming');
}

mixin Flyer {
  void fly() => print('Flying');
}

class Duck with Swimmer, Flyer {}

void main() {
  var donald = Duck();
  donald.swim(); // Swimming
  donald.fly();  // Flying
}

Duck is still empty, but it can swim and fly because it mixed in both abilities. Try that with inheritance and you are stuck, since Duck can only extend one class. This is exactly the case mixins were built for: a capability that shows up across classes that have no business sharing a parent.

There is one wrinkle worth learning now instead of the hard way. When two mixins define a method with the same name, the last one wins. Watch.

mixin Walker {
  String move() => 'walking';
}

mixin Swimmer {
  String move() => 'swimming';
}

class Penguin with Walker, Swimmer {}

void main() {
  print(Penguin().move()); // swimming
}

Both mixins have a move method, and Penguin uses with Walker, Swimmer, so Swimmer is applied last and its version wins. Flip the order to with Swimmer, Walker and it prints walking instead. Dart stacks mixins left to right, and each new one layers over the ones before it. You will not hit this every day, but when a method behaves in a way you did not expect, the order of your with list is the first place to look.

Close up of colorful program code representing reusable Dart methods
A mixin bundles behavior you can reuse across classes without inheritance.

The on keyword: mixins that need a base class

Sometimes a mixin only makes sense on top of a particular kind of object, and it needs to reach into that object’s data. That is what on is for. It restricts a mixin so it can only be mixed into classes that extend a given type, and in exchange the mixin gets to use that type’s fields and methods.

class Animal {
  String name;
  Animal(this.name);
}

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

class Dolphin extends Animal with Swimmer {
  Dolphin(String name) : super(name);
}

void main() {
  Dolphin('Echo').swim(); // Echo is swimming
}

The Swimmer mixin uses name, but Swimmer has no name of its own. The on Animal clause is what makes that legal: it promises Dart that anything using Swimmer is an Animal, so name is guaranteed to exist. Leave off on Animal and Dart refuses to compile the mixin, because it cannot prove name is there. Try to mix Swimmer into a class that does not extend Animal and you get the same refusal. Think of on as the mixin saying I only work on this kind of thing, and here is the reason why.

The Dart 3 error beginners run into

If you are on Dart 3, and you almost certainly are, one change trips up anyone following an older tutorial. You used to be able to drop any plain class after with. Not anymore. Say you write a normal Logger class and try to mix it in.

class Logger {
  void log(String msg) => print('LOG: \$msg');
}

class Service with Logger {} // Error on Dart 3

Dart stops you with a message along the lines of “The class Logger can’t be used as a mixin because it isn’t a mixin class or a pure mixin.” The fix is to say what you mean. If Logger is only ever going to be mixed in, declare it with mixin instead of class. If you genuinely need it to work both as a normal class and as a mixin, use mixin class Logger. Nine times out of ten you want the plain mixin keyword.

mixin Logger {
  void log(String msg) => print('LOG: \$msg');
}

class Service with Logger {}

void main() {
  Service().log('started'); // LOG: started
}

Change one word and the error is gone. This is one of those rules that feels annoying the first time and sensible the tenth, because now the with clause tells you at a glance which things were designed to be mixed in.

Developer desk workspace with monitor and keyboard for coding practice
Build a mixin yourself to feel how the with keyword composes behavior.

When a mixin is the wrong tool

Mixins are easy to overuse once they click. The quick test is about what kind of relationship you actually have. Reach for inheritance when a class truly is a kind of another class, the way an Ebook is a Book. Reach for a mixin when a class needs an ability that has nothing to do with its family tree, the way both a Duck and a Plane can fly without being the same kind of thing. And when you just need one object to use another, that is still plain composition, a field holding the other object, which the Dart inheritance post walks through.

One real limit is worth keeping in mind: a mixin cannot have a constructor, so it is a poor place to store setup data. If a capability needs its own constructor arguments, a mixin will fight you, and a small helper class held as a field is the cleaner answer. Mixins are for shared behavior, not for shared state you have to build up first. In CIS225 this is usually the moment the is-a, has-a, can-do distinction finally lands: extends for is-a, a field for has-a, and a mixin for can-do.

Your next step

Here is the short version. A mixin is a bundle of methods you declare with the mixin keyword and attach to a class with with, no shared parent and no constructor. You can stack several, and when two of them define the same method, the last one in the with list wins. The on keyword pins a mixin to a base type so it can use that type’s fields, and on Dart 3 you need the mixin or mixin class keyword, since a plain class no longer works after with. Use a mixin for a can-do ability shared across unrelated classes, and stay with inheritance or a field when the relationship is really is-a or has-a.

The way to make this stick is to build one. Write a mixin called Serializable with a toJson method that returns a Map, then mix it into two unrelated classes, say a User and a Product, and print the result of each. Add a second mixin and watch both abilities show up on the same class. If constructors still feel shaky underneath all this, the Dart classes post is worth a second read, and the Dart inheritance post is the natural companion to this one, since mixins and extends solve overlapping problems. For every rule Dart enforces, the official Dart mixins guide is the reference to keep open. Now go give one class an ability it was not born with.

Leave a Comment

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