Dart: Mixins – Reusing Code

In Dart, a mixin is a special kind of class that lets you share code between multiple classes without using inheritance. Think of it like a toolbox of reusable methods and properties that you can “mix into” any class. You use the with keyword to add a mixin to a class, and just like that, your class gets extra functionality—without having to extend another class.

You might use mixins when different classes need the same behavior, but they’re not related in a way that makes sense for inheritance. For example, if several classes need logging, animation, or validation features, you can create a mixin for each of those and add them where needed. This helps keep your code cleaner, more organized, and avoids copying the same code into multiple places.

Basic Mixin Syntax

To create a mixin in Dart, you use the mixin keyword. A mixin looks a lot like a class, but it’s meant to be used as an add-on to other classes—not as something you create objects from.

Here’s how you define a mixin:

mixin Greeter {

  void sayHello() {
    print('Hello!');
  }

}

This Greeter mixin has one method called sayHello.

To use this mixin in a class, you add it with the with keyword:

class Person with Greeter {}

void main() {

  var person = Person();
  person.sayHello(); // Output: Hello!

}

In this example, the Person class mixes in the Greeter mixin. That means Person now has access to the sayHello() method—even though it wasn’t defined directly in the class.

So in short:

  • Use mixin to define shared behavior.
  • Use with to apply it to a class.

This keeps your code simple and avoids repeating yourself when different classes need the same functionality.

Applying a Mixin to a Single Class

Let’s see how you can create a mixin and apply it to just one class.

Here’s a simple mixin that adds swimming ability:

mixin Swimmer {

  void swim() {
    print('Swimming through the water!');
  }

}

Now, imagine you have a Fish class that should be able to swim. Instead of writing the swim() method inside the class, you can use the mixin:

class Fish with Swimmer {}

void main() {

  var nemo = Fish();
  nemo.swim(); // Output: Swimming through the water!

}

In this example, Swimmer is the mixin. Fish is the class using the mixin. When we create a Fish object, it automatically has the swim() method—even though it wasn’t written directly in the Fish class.

So, by applying a mixin to a class, that class inherits all the behavior from the mixin, making it easy to reuse code in a clean and simple way.

Using Multiple Mixins

In Dart, you can add more than one mixin to a class by chaining them with commas after the with keyword. This lets your class gain multiple sets of behaviors without needing deep inheritance trees.

Here’s a quick example:

mixin Flyer {

  void move() {
    print('Flying through the sky!');
  }

}

mixin Swimmer {

  void move() {
    print('Swimming in the ocean!');
  }

}

class Duck with Swimmer, Flyer {}

void main() {

  var donald = Duck();
  donald.move(); // Output: Flying through the sky!

}

In this case, we have two mixins: Swimmer and Flyer. Both have a method named move(). When Duck mixes in both, the last one winsFlyer is applied after Swimmer, so its move() method overrides the one from Swimmer.

Order matters: mixins applied later override the methods of those applied earlier if they share the same names.

This allows you to control behavior by arranging mixins in the right order—just like stacking blocks!

Adding Mixins to Classes with Inheritance

In Dart, a class can extend one other class and also use one or more mixins. This means you get the power of both inheritance and code reuse through mixins.

Here’s how it works:

class Animal {

  void eat() {
    print('Eating...');
  }

}

mixin Walker {

  void walk() {
    print('Walking on land.');
  }

}

class Dog extends Animal with Walker {}

void main() {

  var dog = Dog();
  dog.eat();  // Output: Eating...
  dog.walk(); // Output: Walking on land.

}

In this example, Dog extends the Animal class, so it gets the eat() method. Dog also uses the Walker mixin, so it gets the walk() method too.

This shows how a class can inherit behavior from a parent class and reuse extra features from mixins—all in one simple structure.

You can think of it like this: The class inherits from its parent and borrows extra skills from mixins!

Mixins with State (Using Fields)

Mixins in Dart can also have fields (variables)—not just methods. This means a mixin can hold data, keep track of values, or even manage state for the class it’s mixed into.

Let’s look at an example where a mixin tracks how many times something is used:

mixin Counter {

  int _count = 0;

  void increment() {
    _count++;
    print('Count: $_count');
  }

}

This Counter mixin has a private field _count and a method to increase it.

Now we can add this behavior to any class:

class Clicker with Counter {}

void main() {

  var button = Clicker();

  button.increment(); // Count: 1
  button.increment(); // Count: 2

}

In this example, the Clicker class mixes in Counter. Even though _count is a field from the mixin, it becomes part of the class. You can use the increment() method to change and display the counter.

So yes—mixins can store data just like normal classes, and that data becomes available inside the class that uses the mixin. This makes mixins even more powerful for building reusable, stateful behavior!

Restricting Mixin Usage with on Keyword

Sometimes, you want a mixin to work only with certain kinds of classes—for example, classes that already have specific methods or properties. Dart lets you do this using the on keyword.

The on keyword tells Dart:

“This mixin can only be used on classes that extend or implement this type.”

Let’s see an example:

class Animal {

  void breathe() {
    print('Breathing...');
  }

}

mixin Tracker on Animal {

  void track() {
    print('Tracking...');
    breathe(); // Works because this mixin only applies to Animal types
  }

}

Now, if you try to use Tracker on a class that doesn’t extend Animal, Dart will give you an error.

Here’s how to use it correctly:

class Dog extends Animal with Tracker {}

void main() {

  var dog = Dog();

  dog.track();   // Tracking...
  dog.breathe(); // Breathing...

}

In this example, the Tracker mixin can only be used on classes that are Animal or subclasses of it. That’s why we used on Animal in the mixin definition. Inside the mixin, we can safely call breathe(), because Dart knows any class using this mixin must have it.

Using on makes your mixins safer and smarter, since they only get used where they make sense.

Abstract Methods in Mixins

Mixins can also have abstract methods—methods without a body. This means the mixin is saying,
“I expect any class that uses me to provide its own version of this method.”

This is useful when your mixin needs to call certain methods but doesn’t know exactly how they should work.

Here’s an example:

mixin Notifier {

  void notify(); // Abstract method

  void sendAlert() {
    print('Sending alert...');
    notify(); // This will be defined by the class that uses the mixin
  }

}

Now, any class that uses the Notifier mixin must define the notify() method, or it won’t compile:

class EmailService with Notifier {

  @override
  void notify() {
    print('Notifying via email.');
  }

}

void main() {

  var email = EmailService();
  email.sendAlert();
  // Output:
  // Sending alert...
  // Notifying via email.

}

In this example, Notifier has an abstract method notify(). EmailService mixes it in and provides its own version of notify(). When we call sendAlert(), it works because notify() is now defined.

This feature makes mixins even more flexible—you can define what must exist, but let each class decide how it works.

Mixins vs Inheritance

While both mixins and inheritance allow you to share functionality between classes, they are used for different purposes.

  • Inheritance is about “is-a” relationships. For example, a Dog is a Animal, so it can inherit behavior from Animal.
  • Mixins are for horizontal reuse of behavior. They let you add specific functionality to a class without implying an “is-a” relationship. Instead, you say a class has certain behavior.

In short:

  • Inheritance: A Dog is an Animal.
  • Mixin: A Dog has the ability to track or swim (through mixins).

Real-World Use Case Example

Let’s look at a real-world example where we combine several mixins in a Dart class. Imagine you are building a game model where different characters have multiple abilities like swimming, flying, and scoring points. Each of these abilities will be defined in separate mixins, and the characters can use these mixins as needed.

Here’s how this might work:

// Mixin for swimming ability
mixin Swimmer {

  void swim() {
    print('Swimming in the water!');
  }

}

// Mixin for flying ability
mixin Flyer {

  void fly() {
    print('Flying through the sky!');
  }

}

// Mixin for scoring ability
mixin Scorer {

  int _score = 0;

  void score(int points) {

    _score += points;
    print('Scored $points points! Total score: $_score');

  }
}

// GameCharacter class using multiple mixins
class GameCharacter with Swimmer, Flyer, Scorer {

  String name;

  GameCharacter(this.name);

  void showAbilities() {

    print('$name can:');
    swim();
    fly();
    score(10);  // Just an example of scoring

  }

}

void main() {

  var character = GameCharacter('SuperPlayer');
  character.showAbilities();
  // Output:
  // SuperPlayer can:
  // Swimming in the water!
  // Flying through the sky!
  // Scored 10 points! Total score: 10

}

In this example, Swimmer, Flyer, and Scorer are mixins that provide specific abilities. The GameCharacter class mixes in these abilities, meaning it can swim, fly, and score points—all without duplicating code. The showAbilities method demonstrates how the character can perform different actions by simply calling methods from multiple mixins.

Why Is This Useful?

  • Reusability: Each mixin contains focused functionality (swimming, flying, scoring). You can reuse these mixins in other classes that need the same abilities.
  • Clean Structure: The class doesn’t have to inherit everything from a base class, which keeps it clear and simple. You only add what’s needed.
  • Flexibility: You can mix and match behaviors as needed without forcing a rigid class hierarchy.

This pattern of using mixins keeps your code modular and organized, allowing you to easily add or remove abilities from classes without affecting other parts of the code.

Conclusion

Mixins are a powerful tool in Dart that help you organize your code by allowing you to reuse functionality across multiple classes without the need for complicated inheritance. By using mixins, you can keep your code clean, modular, and flexible, while also avoiding repetition.

To recap:

  • Mixins allow you to add reusable behavior to any class.
  • They are great for sharing functionality like logging, validation, or animation without forcing a strict class hierarchy.
  • By breaking shared functionality into smaller mixins, you keep each part of your code focused and easy to maintain.

We encourage you to start experimenting with mixins in your own projects—whether it’s a game model, a Flutter app, or any other codebase. By breaking apart shared functionality into separate mixins, you’ll end up with cleaner and more maintainable code.