Mixins in Dart

By Syncfusion®, Inc. · Last updated

How to add ready-made abilities to classes with mixins, make callable classes, and add methods to existing types like String and List with extensions and extension types.

What Are Mixins?

Mixin = a bundle of methods (and fields) that you can add to any class, without making it a parent.

  • You write it with the mixin keyword.
  • You add it to a class with the with keyword.
  • One class can take many mixins.
  • So a class can gain abilities from many places, while still having only one parent.

Simple lesson: a mixin is a ready-made ability you can plug into a class.

A Real-World Example

Think of badges on a school blazer.

  • Swimming badge, First Aid badge, Debate badge.
  • Any student can earn any badge. A badge is not a parent of the student.
  • Two very different students can wear the same badge.
  • A mixin is a badge: class Ravi extends Student with Swimmer, FirstAider.

Toggle the mixins and see the order Dart uses to find a method:

Every Run box on this page runs on the course's Dart runner in your browser.

In Inheritance you saw that Dart does not allow two parents. Mixins are the answer to that problem.

What You Will Learn

In this lesson, you will learn:

  1. How to write a mixin and add it with with, and which one wins a name clash.
  2. How on limits a mixin, and how mixins compare with inheritance.
  3. How a call() method makes an object callable.
  4. How to add methods to existing types with extensions.
  5. What extension types are, and when to use them.

Creating and Using Mixins

The mixin Keyword

mixin Name {
  // fields and methods
}

class Child extends Parent with Name { }

A swimming ability for anyone

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

class Student {
  final String name;
  Student(this.name);
}

class SwimmingStudent extends Student with Swimmer {
  SwimmingStudent(super.name);
}

class Duck with Swimmer {}

void main() {
  SwimmingStudent('Ravi').swim();
  Duck().swim();
  print(Duck() is Swimmer);
}
  • A student and a duck are not related, but both can swim.
  • A mixin can be used without extends too: class Duck with Swimmer.
  • An object with a mixin is that mixin type: Duck() is Swimmer is true.

Mixins with Fields

A mixin can keep its own data:

A counter you can add to any class

mixin Counter {
  int count = 0;
  void increment() => count++;
}

class Gate with Counter {
  void open() {
    increment();
    print('Gate opened. Total: $count');
  }
}

void main() {
  var g = Gate();
  g.open();
  g.open();
  g.open();
}

Simple lesson: write the ability once as a mixin, then add it with with wherever it is needed.

Multiple Mixins and Their Order

Using Several Mixins

A sports captain with three badges

mixin Runner {
  void run() => print('Running 100 m');
}

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

mixin Leader {
  void lead() => print('Leading the team');
}

class Student {
  final String name;
  Student(this.name);
}

class Captain extends Student with Runner, Swimmer, Leader {
  Captain(super.name);
}

void main() {
  var c = Captain('Meena');
  print('${c.name} can:');
  c.run();
  c.swim();
  c.lead();
}

When Two Mixins Have the Same Method

Mixins are stacked in the order you write them. The last one is on top, so it wins:

Which hello() runs?

The order after with matters when names clash. The last mixin wins.

Try to give mixins different method names, so the order does not surprise anyone.

Simple lesson: many mixins are fine, and if two share a name, the last one listed wins.

The on Keyword

on constraint = mixin Name on SomeClass means the mixin can only be used by classes that extend (or implement) SomeClass.

  • The mixin can then safely use members of SomeClass.
  • Dart stops anyone from adding it to the wrong class.

Only performers can sing on stage

class Performer {
  final String name;
  Performer(this.name);
  void bow() => print('$name bows.');
}

mixin Singer on Performer {
  void sing() {
    print('$name sings the school song.');
    bow();
  }
}

class Student extends Performer with Singer {
  Student(super.name);
}

void main() {
  Student('Priya').sing();
}
  • Singer uses name and bow(). They exist because of on Performer.
  • class Robot with Singer {} would be a compile error: a Robot is not a Performer.

Simple lesson: on says "this badge is only for members of this family".

Mixins, Inheritance and Interfaces

extendsimplementswith (mixin)
How many?onemanymany
Gets code?yesnoyes
Must write every member?noyesno
Meaningis apromises to act likehas the ability
ExampleStudent extends PersonFan implements SwitchableDuck with Swimmer

mixin class

mixin class = a class that can also be used as a mixin. Use it when you want both: objects of it, and its abilities in other classes.

mixin class Logger {
  void log(String m) => print('[log] $m');
}

var l = Logger();           // works: it is a class
class Library with Logger {} // works: it is a mixin

Easy way to remember

  • extends: is a (one family).
  • implements: promises (many contracts, no code).
  • with: can do (many abilities, with code).

Simple lesson: use mixins for shared abilities that do not fit one family tree.

Callable Classes

Callable class = a class with a method named call(). Its objects can be called like functions: obj(5).

A fee calculator you call like a function

class FeeCalculator {
  final int perMonth;
  FeeCalculator(this.perMonth);

  int call(int months) => perMonth * months;
}

void main() {
  var fee = FeeCalculator(1200);
  print(fee(3));
  print(fee(12));
  print(fee.call(1));
}
  • fee(3) is a short way of writing fee.call(3).
  • Unlike a plain function, the object can keep data, like perMonth.

A callable object that remembers

class Attendance {
  final List<String> present = [];

  void call(String name) {
    present.add(name);
    print('$name marked present (${present.length})');
  }
}

void main() {
  var mark = Attendance();
  mark('Asha');
  mark('Ravi');
  mark('Imran');
  print(mark.present);
}

Simple lesson: give a class a call() method, and its objects work like functions that remember things.

What Are Extensions?

Extension method = a new method or getter that you add to an existing type, even one you did not write, like String or int.

  • You cannot open Dart's String class and edit it.
  • But you can write extension ... on String { ... }.
  • After that, every String can use your new method, as if it was always there.

Simple lesson: an extension adds your own tools to a type you do not own.

A Real-World Example

The school gives every student the same calculator.

  • You cannot change how the calculator is built.
  • But you can stick a small card on the back with your own shortcuts: "GPA = total ÷ 5".
  • The calculator is the same. You just added a helper to it.
  • An extension is that card, stuck on String, List or DateTime.

Here is the difference between a helper function and an extension:

Helper function or extension?

Extension types (the last part of this lesson) are shown in plain boxes. The runner does not support them yet.

Extension Methods

Writing One

extension Name on Type {
  // methods and getters, using `this` for the value
}

A shout() method for every String

extension Loud on String {
  String shout() => '${toUpperCase()}!';
}

void main() {
  print('hello'.shout());
  var name = 'asha';
  print(name.shout());
}
  • Inside the extension, this is the value the method was called on.
  • You may leave out this.: toUpperCase() means this.toUpperCase().

Extension Getters

Getters work too, read without ():

Getters on int

extension AgeTools on int {
  bool get isTeen => this >= 13 && this <= 19;
  int get inMonths => this * 12;
}

void main() {
  var age = 14;
  print(age.isTeen);
  print(age.inMonths);
  print(9.isTeen);
}

What happens when you call an extension

Simple lesson: write extension Name on Type, and every value of that type gets your methods.

Naming and Conflicts

Named and Unnamed Extensions

  • Named: extension Loud on String. You can refer to it by name, and import or hide it.
  • Unnamed: extension on String. Usable only in the file where it is written.

An unnamed extension

extension on String {
  String get initials => split(' ').map((w) => w[0]).join();
}

void main() {
  print('Green Valley School'.initials);
  print('Asha Kumari'.initials);
}

When Names Clash

Conflict = two extensions add a member with the same name to the same type.

// in file a.dart
extension A on String { String tidy() => trim(); }
// in file b.dart
extension B on String { String tidy() => toLowerCase(); }

print(' Hi '.tidy());
// Error: A member named 'tidy' is defined in 'A' and 'B', and neither is more specific.

Three ways to fix it:

  1. Call it by name: A(' Hi ').tidy(). The name picks the extension.
  2. Hide one: import 'b.dart' hide B; (see Libraries).
  3. Rename one of the methods.

Calling an extension by name

extension Tidy on String {
  String tidy() => trim().toLowerCase();
}

void main() {
  print(Tidy('  HELLO  ').tidy());
}

A real member always wins over an extension.

If you write extension on String { int get length => 0; }, 'abc'.length still gives 3. Do not reuse names of real members.

Simple lesson: give extensions clear names, and call Name(value).method() when two of them clash.

Practical Extensions

On List and DateTime

Marks tools and date tools

extension MarksTools on List<int> {
  int get total => fold(0, (a, b) => a + b);
  String get average => (total / length).toStringAsFixed(1);
  List<int> above(int limit) => where((m) => m > limit).toList();
}

extension SchoolDate on DateTime {
  bool get isWeekend => weekday == DateTime.saturday || weekday == DateTime.sunday;
  String get short => '$day/$month/$year';
}

void main() {
  var marks = [92, 85, 78, 64];
  print('Total: ${marks.total}');
  print('Average: ${marks.average}');
  print('Above 80: ${marks.above(80)}');

  var d = DateTime(2026, 11, 14);
  print('${d.short} is a weekend: ${d.isWeekend}');
}
  • on List<int> means only lists of int get these tools. A List<String> does not.
  • MarksTools uses fold, length and where of the list, through this.

Good extensions are small and obvious. marks.average is clear.

If an extension needs lots of data of its own, it should probably be a class instead.

Simple lesson: extensions make common jobs on built-in types short and readable.

Extension Types

Extension type = a new type that wraps an existing value, with no extra cost while running.

  • Representation type = the type inside, like int in extension type StudentId(int value).
  • The new type is different from int for Dart's checks, so you cannot mix them up.
  • While running, it is just the int. Nothing extra is stored.
extension type StudentId(int value) {
  bool get isValid => value > 0;
}

extension type BookId(int value) {}

void issueBook(StudentId student, BookId book) {
  print('Book ${book.value} issued to student ${student.value}');
}

void main() {
  var s = StudentId(1042);
  var b = BookId(77);
  issueBook(s, b);   // Book 77 issued to student 1042
  issueBook(b, s);   // Error: The argument type 'BookId' can't be assigned to the parameter type 'StudentId'.
}
  • With plain int, swapping the two IDs would compile and cause a silent bug.
  • With extension types, Dart catches the swap before running.

Extension Methods or Extension Types?

Extension methodExtension type
Adds a new type?noyes
Changes what you can pass where?noyes, StudentId is not int
Typical usehelpers on String, Listsafe IDs, units like Rupees
Cost while runningnonenone

Easy way to remember

  • extension X on T = add tools to every T.
  • extension type X(T v) = make a new label for a T, so values are not mixed up.
  • X(value).method() = pick an extension by name.

Simple lesson: extension methods add tools to a type, and extension types make a new, safer type from an old one.

TL;DR

  1. mixin M + class C with M adds abilities. With many mixins, the last one wins a name clash.
  2. mixin M on P can only be used by classes that are a P.
  3. Callable class = has call(), so obj(x) works like a function.
  4. extension X on String { ... } adds methods and getters to an existing type.
  5. extension type Id(int value) makes a new, safer type over an existing one.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
class C extends A, BTwo parents are fineUse extends A with BA compile error.
var m = Swimmer(); for a mixinA mixin makes objectsUse it with with, or write mixin classA compile error.
Ignoring mixin orderOrder does not matterThe last mixin wins a name clashThe wrong method runs.
Using a mixin's on members in the wrong classon is a hinton is a strict ruleA compile error.
Naming the method Call()Any case worksIt must be exactly call()The object is not callable.
Forgetting on TypeExtensions apply to all typesWrite extension X on StringA compile error.
Naming a member like a real oneThe extension replaces itReal members always winYour method is never called.
Two extensions with the same memberDart picks oneCall Name(value).m(), or hide oneAn ambiguity error.
Heavy logic in an extensionExtensions can hold stateExtensions cannot add fieldsUse a class instead.
Expecting an extension type to be a full classIt is a new classIt is a zero-cost wrapper over one valueWrong expectations about fields.

Check Yourself

Exercises

What to Remember

  • Mixins add ready-made abilities without a second parent.
  • The last mixin in the list wins a name clash.
  • A class with call() makes objects you can call like functions.
  • Extensions add tools to types you did not write, like String and List.
  • Extension types stop values like IDs from being mixed up.

Next: Class Modifiers. You will control who can make, extend or implement your classes.