Skip to content

Dart Mixins — Reusable Behavior Composition

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Dart Mixins. We cover key concepts, practical examples, and best practices to help you master this topic.

Dart mixins are a way to reuse a class's code in multiple class hierarchies by injecting the mixin's implementation into the class, without requiring the class to extend the mixin.

What You Will Learn

  • Defining mixins with the mixin keyword
  • Applying mixins with the with keyword
  • Mixin restrictions with on clause
  • Combining multiple mixins
  • Comparing mixins to inheritance and interfaces
  • Using mixins in Flutter widgets

Why It Matters

Mixins solve the problem of sharing behavior across unrelated classes without forcing them into a single inheritance hierarchy. This is especially useful in Flutter, where many built-in mixins provide capabilities like AutomaticKeepAliveClientMixin, TickerProviderStateMixin, and SingleTickerProviderStateMixin. Without mixins, you would either duplicate code across classes or force artificial inheritance relationships.

Real-World Use

The DodaTech Flutter app uses a LoggingMixin that adds timestamped logging to any class. The ValidationMixin provides form validation methods that are mixed into multiple form widgets. The AnalyticsMixin adds event tracking to screens without requiring them to extend a common base class.

Learning Path

flowchart LR
  A[Dart Interfaces] --> B[Dart Mixins\nYou are here]
  B --> C[Dart Generics]
  style B fill:#f90,color:#fff

Defining and Using Mixins

A mixin is defined with the mixin keyword and applied with with:

mixin LoggerMixin {
  void log(String message) {
    print('[LOG]: $message');
  }

  void error(String message) {
    print('[ERROR]: $message');
  }
}

class UserService with LoggerMixin {
  void createUser(String name) {
    log('Creating user: $name');
    // Business logic...
    log('User created: $name');
  }
}

class OrderService with LoggerMixin {
  void placeOrder(String item) {
    log('Placing order for: $item');
    // Business logic...
    log('Order placed: $item');
  }
}

void main() {
  var userService = UserService();
  userService.createUser('Alice');

  var orderService = OrderService();
  orderService.placeOrder('Widget');
}

Output:

[LOG]: Creating user: Alice
[LOG]: User created: Alice
[LOG]: Placing order for: Widget
[LOG]: Order placed: Widget

Both UserService and OrderService get the log and error methods without extending a common base class. The mixin is injected into each class independently.

Mixin with State

Mixins can have fields and state, unlike interfaces:

mixin CounterMixin {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
  }

  void reset() {
    _count = 0;
  }
}

class CounterWidget with CounterMixin {
  void display() {
    print('Current count: $count');
  }
}

void main() {
  var widget = CounterWidget();
  widget.display(); // 0
  widget.increment();
  widget.increment();
  widget.increment();
  widget.display(); // 3
  widget.reset();
  widget.display(); // 0
}

Output:

Current count: 0
Current count: 3
Current count: 0

The mixin maintains its own _count field. Each class that uses the mixin gets its own copy of the field. This is more stateful than interfaces but more flexible than inheritance.

Multiple Mixins

A class can use multiple mixins, combining their behaviors:

mixin TimestampMixin {
  DateTime get createdAt => DateTime.now();
}

mixin IdMixin {
  String get id => DateTime.now().millisecondsSinceEpoch.toString();
}

mixin JsonSerializableMixin {
  Map<String, dynamic> toJson();
}

class User with TimestampMixin, IdMixin, JsonSerializableMixin {
  String name;
  int age;

  User(this.name, this.age);

  @override
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'age': age,
    'createdAt': createdAt.toIso8601String(),
  };
}

void main() {
  var user = User('Alice', 30);
  print(user.toJson());
}

Output:

{id: 1719523200000, name: Alice, age: 30, createdAt: 2026-06-28T...}

Multiple mixins are separated by commas after with. The class inherits all methods and fields from all mixins.

Mixin Restrictions (on Clause)

The on clause restricts which classes can use the mixin:

class Animal {
  String name = '';
  void breathe() => print('Breathing');
}

mixin Walker on Animal {
  void walk() {
    print('$name is walking');
  }
}

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

// Dog can use Walker and Swimmer because it extends Animal
class Dog extends Animal with Walker, Swimmer {
  Dog(String name) {
    this.name = name;
  }
}

// Bird can use Walker but not Swimmer
class Bird extends Animal with Walker {
  Bird(String name) {
    this.name = name;
  }
}

// This would fail: String does not extend Animal
// class Fish extends String with Swimmer {}

void main() {
  var dog = Dog('Buddy');
  dog.walk();
  dog.swim();
  dog.breathe();

  var bird = Bird('Tweety');
  bird.walk();
}

Output:

Buddy is walking
Buddy is swimming
Breathing
Tweety is walking

The on Animal clause means only classes that extend Animal can use the Walker or Swimmer mixins. This ensures the mixin can safely access Animal's members.

Mixin Ordering and Super Calls

When multiple mixins are applied, the order matters. If a mixin calls super, the call goes to the next mixin in the chain:

mixin A {
  String getMessage() => 'A';
}

mixin B on A {
  @override
  String getMessage() => 'B -> ${super.getMessage()}';
}

mixin C on B {
  @override
  String getMessage() => 'C -> ${super.getMessage()}';
}

class MyClass with A, B, C {
  void printMessage() => print(getMessage());
}

void main() {
  var obj = MyClass();
  obj.printMessage();
}

Output:

C -> B -> A

The method calls are resolved from right to left in the with clause, and super calls go from left to right. This is called the linearization of mixins.

Mixins vs Inheritance vs Interfaces

Understanding when to use each:

// Inheritance: is-a relationship, shares implementation
class Vehicle {
  void move() => print('Moving');
}
class Car extends Vehicle {}

// Interface: can-do relationship, no shared implementation
abstract class Flyable {
  void fly();
}
class Airplane implements Flyable {
  @override
  void fly() => print('Flying');
}

// Mixin: has-a relationship, shares implementation without inheritance
mixin Electric {
  void charge() => print('Charging battery');
}
class Tesla extends Car with Electric {}

void main() {
  var tesla = Tesla();
  tesla.move(); // From Vehicle
  tesla.charge(); // From Electric
}

Output:

Moving
Charging battery

Use extends for is-a relationships. Use implements for can-do contracts. Use with for has-a reusable behavior.

Common Flutter Mixins

Flutter provides several useful mixins out of the box:

import 'package:flutter/material.dart';

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin {

  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    // SingleTickerProviderStateMixin provides this
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this, // Provided by the mixin
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

SingleTickerProviderStateMixin provides the TickerProvider interface required by AnimationController. Without the mixin, you would need to manually manage a Ticker object.

Custom Mixin Example: Validation

A practical mixin for form validation:

mixin ValidationMixin {
  String? validateEmail(String? email) {
    if (email == null || email.isEmpty) return 'Email is required';
    if (!email.contains('@') || !email.contains('.')) {
      return 'Enter a valid email address';
    }
    return null;
  }

  String? validatePassword(String? password) {
    if (password == null || password.isEmpty) return 'Password is required';
    if (password.length < 8) return 'Password must be at least 8 characters';
    if (!password.contains(RegExp(r'[A-Z]'))) {
      return 'Password must contain an uppercase letter';
    }
    if (!password.contains(RegExp(r'[0-9]'))) {
      return 'Password must contain a digit';
    }
    return null;
  }

  String? validateNotEmpty(String? value, String fieldName) {
    if (value == null || value.trim().isEmpty) {
      return '$fieldName cannot be empty';
    }
    return null;
  }
}

class RegistrationForm with ValidationMixin {
  void submit(String email, String password) {
    var emailError = validateEmail(email);
    var passwordError = validatePassword(password);

    if (emailError != null || passwordError != null) {
      print('Validation failed:');
      if (emailError != null) print('  - $emailError');
      if (passwordError != null) print('  - $passwordError');
    } else {
      print('Registration successful for $email');
    }
  }
}

void main() {
  var form = RegistrationForm();
  form.submit('invalid', 'weak');
  print('---');
  form.submit('alice@test.com', 'StrongPass1');
}

Output:

Validation failed:
  - Enter a valid email address
  - Password must be at least 8 characters
---
Registration successful for alice@test.com

Common Mistakes

  1. Using mixins when a simple function suffices: If the mixin has no state and no super calls, a top-level function or static method may be simpler.

  2. Assuming mixins create an is-a relationship: Mixins provide behavior, not identity. obj is LoggerMixin returns true, but the intent is code reuse, not type hierarchy.

  3. Order sensitivity with overlapping methods: When two mixins define the same method, the rightmost mixin in the with clause wins. Changing the order changes behavior.

  4. Not using on clause when the mixin depends on superclass members: If a mixin accesses this.someMethod(), it should declare on the class that provides that method to prevent runtime errors.

  5. Overusing mixins for everything: Mixins are powerful but can make code hard to follow when overused. Prefer composition and simple inheritance for most cases.

Practice Questions

  1. What is the difference between a mixin and an interface?
  2. How does the on clause restrict which classes can use a mixin?
  3. What happens when two mixins define the same method?
  4. Why does Flutter's SingleTickerProviderStateMixin use the on clause with State?
  5. Challenge: Design an AuditMixin that logs every method call with a timestamp. Use noSuchMethod or runtime introspection to automatically log all invocations. Apply the mixin to a BankAccount class.

Mini Project

Build a logging and monitoring system with mixins:

  • TimestampMixin that adds creation timestamp
  • SerializationMixin that adds toJson() and fromJson()
  • CacheMixin that adds in-memory Caching
  • Combine all three into a CachedApiClient
  • Apply individual mixins to different classes
  • Write tests verifying each mixin's behavior independently

FAQ

Can a mixin extend another mixin?

Yes. A mixin can use on to specify that it depends on another mixin. This creates a mixin chain where the dependent mixin can access the base mixin's members.

Can I use a class as a mixin?

Yes. Any class can be used as a mixin with with, but the class must satisfy the mixin requirements (no declared constructor, no super calls to non-Object methods). The mixin keyword is preferred for clarity.

Can a mixin have a constructor?

No. Mixins cannot have constructors. Fields must be initialized at declaration or be nullable/late.

How do mixins interact with `super`?

A mixin can call super.methodName(). The call resolves to the next class or mixin in the linearization order, which depends on the declaration order in the with clause.

Are mixins used in production Dart code?

Yes. Mixins are extensively used in Flutter (ticker providers, automatic keep-alive, sliver persistence) and in many Dart packages.

What is Next

Now that you understand mixins, learn about type-safe generic programming. Proceed to Dart Generics for parameterized types and collection type safety. Then explore Async Programming in Dart for futures and async-await.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro